From 17d57231c4d9d3dfb486b6d37b85d57e6065958e Mon Sep 17 00:00:00 2001 From: Eric Wong Date: Thu, 16 Mar 2017 03:16:51 +0000 Subject: tcp_info: support this struct under FreeBSD Of course these fields are not portable between Linux and FreeBSD, but they should remain ABI-compatible for future versions of each OS. Tested on FreeBSD 10.3-RELEASE i386 TCP state names will be another problem... --- ext/raindrops/extconf.rb | 78 +++++++++++++++- ext/raindrops/linux_tcp_info.c | 196 ----------------------------------------- ext/raindrops/raindrops.c | 8 +- ext/raindrops/tcp_info.c | 195 ++++++++++++++++++++++++++++++++++++++++ test/test_linux_tcp_info.rb | 68 -------------- test/test_tcp_info.rb | 85 ++++++++++++++++++ 6 files changed, 363 insertions(+), 267 deletions(-) delete mode 100644 ext/raindrops/linux_tcp_info.c create mode 100644 ext/raindrops/tcp_info.c delete mode 100644 test/test_linux_tcp_info.rb create mode 100644 test/test_tcp_info.rb diff --git a/ext/raindrops/extconf.rb b/ext/raindrops/extconf.rb index 79d212c..5273b74 100644 --- a/ext/raindrops/extconf.rb +++ b/ext/raindrops/extconf.rb @@ -1,4 +1,5 @@ require 'mkmf' +require 'shellwords' dir_config('atomic_ops') have_func('mmap', 'sys/mman.h') or abort 'mmap() not found' @@ -6,9 +7,83 @@ have_func('munmap', 'sys/mman.h') or abort 'munmap() not found' $CPPFLAGS += " -D_GNU_SOURCE " have_func('mremap', 'sys/mman.h') -have_header('linux/tcp.h') +headers = %w(sys/types.h netdb.h string.h sys/socket.h netinet/in.h) +if have_header('linux/tcp.h') + headers << 'linux/tcp.h' +else + %w(netinet/tcp.h netinet/tcp_fsm.h).each { |h| + have_header(h, headers) and headers << h + } +end $CPPFLAGS += " -D_BSD_SOURCE " + +if have_type("struct tcp_info", headers) + %w( + tcpi_state + tcpi_ca_state + tcpi_retransmits + tcpi_probes + tcpi_backoff + tcpi_options + tcpi_snd_wscale + tcpi_rcv_wscale + tcpi_rto + tcpi_ato + tcpi_snd_mss + tcpi_rcv_mss + tcpi_unacked + tcpi_sacked + tcpi_lost + tcpi_retrans + tcpi_fackets + tcpi_last_data_sent + tcpi_last_ack_sent + tcpi_last_data_recv + tcpi_last_ack_recv + tcpi_pmtu + tcpi_rcv_ssthresh + tcpi_rtt + tcpi_rttvar + tcpi_snd_ssthresh + tcpi_snd_cwnd + tcpi_advmss + tcpi_reordering + tcpi_rcv_rtt + tcpi_rcv_space + tcpi_total_retrans + tcpi_snd_wnd + tcpi_snd_bwnd + tcpi_snd_nxt + tcpi_rcv_nxt + tcpi_toe_tid + tcpi_snd_rexmitpack + tcpi_rcv_ooopack + tcpi_snd_zerowin + ).each do |field| + cfunc = "tcp_info_#{field}" + if have_struct_member('struct tcp_info', field, headers) + func_body = <#{field}); +} +EOF + func_body.delete!("\n") + $defs << "-DCFUNC_#{cfunc}=#{Shellwords.shellescape(func_body)}" + else + func_body = "static inline void #{cfunc}(void) {}" + $defs << "-DCFUNC_#{cfunc}=#{Shellwords.shellescape(func_body)}" + cfunc = 'rb_f_notimplement'.freeze + end + rbmethod = %Q("#{field.sub(/\Atcpi_/, ''.freeze)}") + $defs << "-DDEFINE_METHOD_tcp_info_#{field}=" \ + "#{Shellwords.shellescape( + %Q[rb_define_method(cTCP_Info,#{rbmethod},#{cfunc},0)])}" + end +end + have_func("getpagesize", "unistd.h") have_func('rb_thread_blocking_region') have_func('rb_thread_io_blocking_region') @@ -53,4 +128,5 @@ Users of Debian-based distros may run: apt-get install libatomic-ops-dev SRC +create_header # generate extconf.h to avoid excessively long command-line create_makefile('raindrops_ext') diff --git a/ext/raindrops/linux_tcp_info.c b/ext/raindrops/linux_tcp_info.c deleted file mode 100644 index 83001a5..0000000 --- a/ext/raindrops/linux_tcp_info.c +++ /dev/null @@ -1,196 +0,0 @@ -#if defined(__linux__) && defined(HAVE_LINUX_TCP_H) -#include -#include -#include -#include -#ifdef TCP_INFO -#include "my_fileno.h" - -#define TCPI_ATTR_READER(x) \ -static VALUE tcp_info_##x(VALUE self) \ -{ \ - struct tcp_info *info = DATA_PTR(self); \ - return UINT2NUM((uint32_t)info->tcpi_##x); \ -} - -TCPI_ATTR_READER(state) -TCPI_ATTR_READER(ca_state) -TCPI_ATTR_READER(retransmits) -TCPI_ATTR_READER(probes) -TCPI_ATTR_READER(backoff) -TCPI_ATTR_READER(options) -TCPI_ATTR_READER(snd_wscale) -TCPI_ATTR_READER(rcv_wscale) -TCPI_ATTR_READER(rto) -TCPI_ATTR_READER(ato) -TCPI_ATTR_READER(snd_mss) -TCPI_ATTR_READER(rcv_mss) -TCPI_ATTR_READER(unacked) -TCPI_ATTR_READER(sacked) -TCPI_ATTR_READER(lost) -TCPI_ATTR_READER(retrans) -TCPI_ATTR_READER(fackets) -TCPI_ATTR_READER(last_data_sent) -TCPI_ATTR_READER(last_ack_sent) -TCPI_ATTR_READER(last_data_recv) -TCPI_ATTR_READER(last_ack_recv) -TCPI_ATTR_READER(pmtu) -TCPI_ATTR_READER(rcv_ssthresh) -TCPI_ATTR_READER(rtt) -TCPI_ATTR_READER(rttvar) -TCPI_ATTR_READER(snd_ssthresh) -TCPI_ATTR_READER(snd_cwnd) -TCPI_ATTR_READER(advmss) -TCPI_ATTR_READER(reordering) -TCPI_ATTR_READER(rcv_rtt) -TCPI_ATTR_READER(rcv_space) -TCPI_ATTR_READER(total_retrans) - -static size_t tcpi_memsize(const void *ptr) -{ - return sizeof(struct tcp_info); -} - -static const rb_data_type_t tcpi_type = { - "tcp_info", - { NULL, RUBY_TYPED_DEFAULT_FREE, tcpi_memsize, /* reserved */ }, - /* parent, data, [ flags ] */ -}; - -static VALUE alloc(VALUE klass) -{ - struct tcp_info *info; - - return TypedData_Make_Struct(klass, struct tcp_info, &tcpi_type, info); -} - -/* - * call-seq: - * - * Raindrops::TCP_Info.new(tcp_socket) -> TCP_Info object - * - * Reads a TCP_Info object from any given +tcp_socket+. See the tcp(7) - * manpage and /usr/include/linux/tcp.h for more details. - */ -static VALUE init(VALUE self, VALUE io) -{ - int fd = my_fileno(io); - struct tcp_info *info = DATA_PTR(self); - socklen_t len = (socklen_t)sizeof(struct tcp_info); - int rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, info, &len); - - if (rc != 0) - rb_sys_fail("getsockopt"); - - return self; -} - -void Init_raindrops_linux_tcp_info(void) -{ - VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject); - VALUE cTCP_Info; - - /* - * Document-class: Raindrops::TCP_Info - * - * This is used to wrap "struct tcp_info" as described in tcp(7) - * and /usr/include/linux/tcp.h. The following readers methods - * are defined corresponding to the "tcpi_" fields in the - * tcp_info struct. - * - * In particular, the +last_data_recv+ field is useful for measuring - * the amount of time a client spent in the listen queue before - * +accept()+, but only if +TCP_DEFER_ACCEPT+ is used with the - * listen socket (it is on by default in Unicorn). - * - * - state - * - ca_state - * - retransmits - * - probes - * - backoff - * - options - * - snd_wscale - * - rcv_wscale - * - rto - * - ato - * - snd_mss - * - rcv_mss - * - unacked - * - sacked - * - lost - * - retrans - * - fackets - * - last_data_sent - * - last_ack_sent - * - last_data_recv - * - last_ack_recv - * - pmtu - * - rcv_ssthresh - * - rtt - * - rttvar - * - snd_ssthresh - * - snd_cwnd - * - advmss - * - reordering - * - rcv_rtt - * - rcv_space - * - total_retrans - * - * https://kernel.org/doc/man-pages/online/pages/man7/tcp.7.html - */ - cTCP_Info = rb_define_class_under(cRaindrops, "TCP_Info", rb_cObject); - rb_define_alloc_func(cTCP_Info, alloc); - rb_define_private_method(cTCP_Info, "initialize", init, 1); - - /* - * Document-method: Raindrops::TCP_Info#get! - * - * call-seq: - * - * info = Raindrops::TCP_Info.new(tcp_socket) - * info.get!(tcp_socket) - * - * Update an existing TCP_Info objects with the latest stats - * from the given socket. This even allows sharing TCP_Info - * objects between different sockets to avoid garbage. - */ - rb_define_method(cTCP_Info, "get!", init, 1); - -#define TCPI_DEFINE_METHOD(x) \ - rb_define_method(cTCP_Info, #x, tcp_info_##x, 0) - - TCPI_DEFINE_METHOD(state); - TCPI_DEFINE_METHOD(ca_state); - TCPI_DEFINE_METHOD(retransmits); - TCPI_DEFINE_METHOD(probes); - TCPI_DEFINE_METHOD(backoff); - TCPI_DEFINE_METHOD(options); - TCPI_DEFINE_METHOD(snd_wscale); - TCPI_DEFINE_METHOD(rcv_wscale); - TCPI_DEFINE_METHOD(rto); - TCPI_DEFINE_METHOD(ato); - TCPI_DEFINE_METHOD(snd_mss); - TCPI_DEFINE_METHOD(rcv_mss); - TCPI_DEFINE_METHOD(unacked); - TCPI_DEFINE_METHOD(sacked); - TCPI_DEFINE_METHOD(lost); - TCPI_DEFINE_METHOD(retrans); - TCPI_DEFINE_METHOD(fackets); - TCPI_DEFINE_METHOD(last_data_sent); - TCPI_DEFINE_METHOD(last_ack_sent); - TCPI_DEFINE_METHOD(last_data_recv); - TCPI_DEFINE_METHOD(last_ack_recv); - TCPI_DEFINE_METHOD(pmtu); - TCPI_DEFINE_METHOD(rcv_ssthresh); - TCPI_DEFINE_METHOD(rtt); - TCPI_DEFINE_METHOD(rttvar); - TCPI_DEFINE_METHOD(snd_ssthresh); - TCPI_DEFINE_METHOD(snd_cwnd); - TCPI_DEFINE_METHOD(advmss); - TCPI_DEFINE_METHOD(reordering); - TCPI_DEFINE_METHOD(rcv_rtt); - TCPI_DEFINE_METHOD(rcv_space); - TCPI_DEFINE_METHOD(total_retrans); -} -#endif /* TCP_INFO */ -#endif /* defined(__linux__) && defined(HAVE_LINUX_TCP_H) */ diff --git a/ext/raindrops/raindrops.c b/ext/raindrops/raindrops.c index 390b8b8..0ea3e32 100644 --- a/ext/raindrops/raindrops.c +++ b/ext/raindrops/raindrops.c @@ -336,7 +336,9 @@ static VALUE aref(VALUE self, VALUE index) #ifdef __linux__ void Init_raindrops_linux_inet_diag(void); -void Init_raindrops_linux_tcp_info(void); +#endif +#ifdef HAVE_TYPE_STRUCT_TCP_INFO +void Init_raindrops_tcp_info(void); #endif #ifndef _SC_NPROCESSORS_CONF @@ -441,6 +443,8 @@ void Init_raindrops_ext(void) #ifdef __linux__ Init_raindrops_linux_inet_diag(); - Init_raindrops_linux_tcp_info(); +#endif +#ifdef HAVE_TYPE_STRUCT_TCP_INFO + Init_raindrops_tcp_info(); #endif } diff --git a/ext/raindrops/tcp_info.c b/ext/raindrops/tcp_info.c new file mode 100644 index 0000000..dc615f7 --- /dev/null +++ b/ext/raindrops/tcp_info.c @@ -0,0 +1,195 @@ +#include +#include "extconf.h" +#include +#include +#if defined(HAVE_LINUX_TCP_H) +# include +#else +# if defined(HAVE_NETINET_TCP_H) +# include +# endif +# if defined(HAVE_NETINET_TCP_FSM_H) +# include +# endif +#endif + +#ifdef HAVE_TYPE_STRUCT_TCP_INFO +#include "my_fileno.h" + +CFUNC_tcp_info_tcpi_state +CFUNC_tcp_info_tcpi_ca_state +CFUNC_tcp_info_tcpi_retransmits +CFUNC_tcp_info_tcpi_probes +CFUNC_tcp_info_tcpi_backoff +CFUNC_tcp_info_tcpi_options +CFUNC_tcp_info_tcpi_snd_wscale +CFUNC_tcp_info_tcpi_rcv_wscale +CFUNC_tcp_info_tcpi_rto +CFUNC_tcp_info_tcpi_ato +CFUNC_tcp_info_tcpi_snd_mss +CFUNC_tcp_info_tcpi_rcv_mss +CFUNC_tcp_info_tcpi_unacked +CFUNC_tcp_info_tcpi_sacked +CFUNC_tcp_info_tcpi_lost +CFUNC_tcp_info_tcpi_retrans +CFUNC_tcp_info_tcpi_fackets +CFUNC_tcp_info_tcpi_last_data_sent +CFUNC_tcp_info_tcpi_last_ack_sent +CFUNC_tcp_info_tcpi_last_data_recv +CFUNC_tcp_info_tcpi_last_ack_recv +CFUNC_tcp_info_tcpi_pmtu +CFUNC_tcp_info_tcpi_rcv_ssthresh +CFUNC_tcp_info_tcpi_rtt +CFUNC_tcp_info_tcpi_rttvar +CFUNC_tcp_info_tcpi_snd_ssthresh +CFUNC_tcp_info_tcpi_snd_cwnd +CFUNC_tcp_info_tcpi_advmss +CFUNC_tcp_info_tcpi_reordering +CFUNC_tcp_info_tcpi_rcv_rtt +CFUNC_tcp_info_tcpi_rcv_space +CFUNC_tcp_info_tcpi_total_retrans + +static size_t tcpi_memsize(const void *ptr) +{ + return sizeof(struct tcp_info); +} + +static const rb_data_type_t tcpi_type = { + "tcp_info", + { NULL, RUBY_TYPED_DEFAULT_FREE, tcpi_memsize, /* reserved */ }, + /* parent, data, [ flags ] */ +}; + +static VALUE alloc(VALUE klass) +{ + struct tcp_info *info; + + return TypedData_Make_Struct(klass, struct tcp_info, &tcpi_type, info); +} + +/* + * call-seq: + * + * Raindrops::TCP_Info.new(tcp_socket) -> TCP_Info object + * + * Reads a TCP_Info object from any given +tcp_socket+. See the tcp(7) + * manpage and /usr/include/linux/tcp.h for more details. + */ +static VALUE init(VALUE self, VALUE io) +{ + int fd = my_fileno(io); + struct tcp_info *info = DATA_PTR(self); + socklen_t len = (socklen_t)sizeof(struct tcp_info); + int rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, info, &len); + + if (rc != 0) + rb_sys_fail("getsockopt"); + + return self; +} + +void Init_raindrops_tcp_info(void) +{ + VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject); + VALUE cTCP_Info; + + /* + * Document-class: Raindrops::TCP_Info + * + * This is used to wrap "struct tcp_info" as described in tcp(7) + * and /usr/include/linux/tcp.h. The following readers methods + * are defined corresponding to the "tcpi_" fields in the + * tcp_info struct. + * + * In particular, the +last_data_recv+ field is useful for measuring + * the amount of time a client spent in the listen queue before + * +accept()+, but only if +TCP_DEFER_ACCEPT+ is used with the + * listen socket (it is on by default in Unicorn). + * + * - state + * - ca_state + * - retransmits + * - probes + * - backoff + * - options + * - snd_wscale + * - rcv_wscale + * - rto + * - ato + * - snd_mss + * - rcv_mss + * - unacked + * - sacked + * - lost + * - retrans + * - fackets + * - last_data_sent + * - last_ack_sent + * - last_data_recv + * - last_ack_recv + * - pmtu + * - rcv_ssthresh + * - rtt + * - rttvar + * - snd_ssthresh + * - snd_cwnd + * - advmss + * - reordering + * - rcv_rtt + * - rcv_space + * - total_retrans + * + * https://kernel.org/doc/man-pages/online/pages/man7/tcp.7.html + */ + cTCP_Info = rb_define_class_under(cRaindrops, "TCP_Info", rb_cObject); + rb_define_alloc_func(cTCP_Info, alloc); + rb_define_private_method(cTCP_Info, "initialize", init, 1); + + /* + * Document-method: Raindrops::TCP_Info#get! + * + * call-seq: + * + * info = Raindrops::TCP_Info.new(tcp_socket) + * info.get!(tcp_socket) + * + * Update an existing TCP_Info objects with the latest stats + * from the given socket. This even allows sharing TCP_Info + * objects between different sockets to avoid garbage. + */ + rb_define_method(cTCP_Info, "get!", init, 1); + + DEFINE_METHOD_tcp_info_tcpi_state; + DEFINE_METHOD_tcp_info_tcpi_ca_state; + DEFINE_METHOD_tcp_info_tcpi_retransmits; + DEFINE_METHOD_tcp_info_tcpi_probes; + DEFINE_METHOD_tcp_info_tcpi_backoff; + DEFINE_METHOD_tcp_info_tcpi_options; + DEFINE_METHOD_tcp_info_tcpi_snd_wscale; + DEFINE_METHOD_tcp_info_tcpi_rcv_wscale; + DEFINE_METHOD_tcp_info_tcpi_rto; + DEFINE_METHOD_tcp_info_tcpi_ato; + DEFINE_METHOD_tcp_info_tcpi_snd_mss; + DEFINE_METHOD_tcp_info_tcpi_rcv_mss; + DEFINE_METHOD_tcp_info_tcpi_unacked; + DEFINE_METHOD_tcp_info_tcpi_sacked; + DEFINE_METHOD_tcp_info_tcpi_lost; + DEFINE_METHOD_tcp_info_tcpi_retrans; + DEFINE_METHOD_tcp_info_tcpi_fackets; + DEFINE_METHOD_tcp_info_tcpi_last_data_sent; + DEFINE_METHOD_tcp_info_tcpi_last_ack_sent; + DEFINE_METHOD_tcp_info_tcpi_last_data_recv; + DEFINE_METHOD_tcp_info_tcpi_last_ack_recv; + DEFINE_METHOD_tcp_info_tcpi_pmtu; + DEFINE_METHOD_tcp_info_tcpi_rcv_ssthresh; + DEFINE_METHOD_tcp_info_tcpi_rtt; + DEFINE_METHOD_tcp_info_tcpi_rttvar; + DEFINE_METHOD_tcp_info_tcpi_snd_ssthresh; + DEFINE_METHOD_tcp_info_tcpi_snd_cwnd; + DEFINE_METHOD_tcp_info_tcpi_advmss; + DEFINE_METHOD_tcp_info_tcpi_reordering; + DEFINE_METHOD_tcp_info_tcpi_rcv_rtt; + DEFINE_METHOD_tcp_info_tcpi_rcv_space; + DEFINE_METHOD_tcp_info_tcpi_total_retrans; +} +#endif /* HAVE_STRUCT_TCP_INFO */ diff --git a/test/test_linux_tcp_info.rb b/test/test_linux_tcp_info.rb deleted file mode 100644 index c947211..0000000 --- a/test/test_linux_tcp_info.rb +++ /dev/null @@ -1,68 +0,0 @@ -# -*- encoding: binary -*- -require 'test/unit' -require 'tempfile' -require 'raindrops' -require 'socket' -require 'pp' -$stderr.sync = $stdout.sync = true -class TestLinuxTCP_Info < Test::Unit::TestCase - - TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' - - # Linux kernel commit 5ee3afba88f5a79d0bff07ddd87af45919259f91 - TCP_INFO_useful_listenq = `uname -r`.strip >= '2.6.24' - - - def test_tcp_server - s = TCPServer.new(TEST_ADDR, 0) - rv = Raindrops::TCP_Info.new s - c = TCPSocket.new TEST_ADDR, s.addr[1] - tmp = Raindrops::TCP_Info.new s - TCP_INFO_useful_listenq and assert_equal 1, tmp.unacked - - assert_equal 0, rv.unacked - a = s.accept - tmp = Raindrops::TCP_Info.new s - assert_equal 0, tmp.unacked - before = tmp.object_id - - tmp.get!(s) - assert_equal before, tmp.object_id - - ensure - c.close if c - a.close if a - s.close - end - - def test_accessors - s = TCPServer.new TEST_ADDR, 0 - tmp = Raindrops::TCP_Info.new s - tcp_info_methods = tmp.methods - Object.new.methods - assert tcp_info_methods.size >= 32 - tcp_info_methods.each do |m| - next if m.to_sym == :get! - val = tmp.__send__ m - assert_kind_of Integer, val - assert val >= 0 - end - ensure - s.close - end - - def test_tcp_server_delayed - delay = 0.010 - delay_ms = (delay * 1000).to_i - s = TCPServer.new(TEST_ADDR, 0) - c = TCPSocket.new TEST_ADDR, s.addr[1] - c.syswrite "." - sleep(delay * 1.2) - a = s.accept - i = Raindrops::TCP_Info.new(a) - assert i.last_data_recv >= delay_ms, "#{i.last_data_recv} < #{delay_ms}" - ensure - c.close if c - a.close if a - s.close - end -end if RUBY_PLATFORM =~ /linux/ diff --git a/test/test_tcp_info.rb b/test/test_tcp_info.rb new file mode 100644 index 0000000..15df087 --- /dev/null +++ b/test/test_tcp_info.rb @@ -0,0 +1,85 @@ +# -*- encoding: binary -*- +require 'test/unit' +require 'tempfile' +require 'raindrops' +require 'socket' +require 'pp' +$stderr.sync = $stdout.sync = true +class TestTCP_Info < Test::Unit::TestCase + + TEST_ADDR = ENV['UNICORN_TEST_ADDR'] || '127.0.0.1' + + # Linux kernel commit 5ee3afba88f5a79d0bff07ddd87af45919259f91 + TCP_INFO_useful_listenq = `uname -r`.strip >= '2.6.24' + + def test_tcp_server_unacked + return if RUBY_PLATFORM !~ /linux/ # unacked not implemented on others... + s = TCPServer.new(TEST_ADDR, 0) + rv = Raindrops::TCP_Info.new s + c = TCPSocket.new TEST_ADDR, s.addr[1] + tmp = Raindrops::TCP_Info.new s + TCP_INFO_useful_listenq and assert_equal 1, tmp.unacked + + assert_equal 0, rv.unacked + a = s.accept + tmp = Raindrops::TCP_Info.new s + assert_equal 0, tmp.unacked + before = tmp.object_id + + tmp.get!(s) + assert_equal before, tmp.object_id + + ensure + [ c, a, s ].compact.each(&:close) + end + + def test_accessors + s = TCPServer.new TEST_ADDR, 0 + tmp = Raindrops::TCP_Info.new s + tcp_info_methods = tmp.methods - Object.new.methods + assert tcp_info_methods.size >= 32 + tcp_info_methods.each do |m| + next if m.to_sym == :get! + next if ! tmp.respond_to?(m) + val = tmp.__send__ m + assert_kind_of Integer, val + assert val >= 0 + end + assert tmp.respond_to?(:state), 'every OS knows about TCP state, right?' + ensure + s.close + end + + def test_tcp_server_delayed + delay = 0.010 + delay_ms = (delay * 1000).to_i + s = TCPServer.new(TEST_ADDR, 0) + c = TCPSocket.new TEST_ADDR, s.addr[1] + c.syswrite "." + sleep(delay * 1.2) + a = s.accept + i = Raindrops::TCP_Info.new(a) + assert i.last_data_recv >= delay_ms, "#{i.last_data_recv} < #{delay_ms}" + ensure + c.close if c + a.close if a + s.close + end + + def test_tcp_server_state_closed + s = TCPServer.new(TEST_ADDR, 0) + c = TCPSocket.new(TEST_ADDR, s.addr[1]) + i = Raindrops::TCP_Info.allocate + a = s.accept + i.get!(a) + state = i.state + c = c.close + sleep(0.01) # wait for kernel to update state + i.get!(a) + assert_not_equal state, i.state + ensure + s.close if s + c.close if c + a.close if a + end +end if defined? Raindrops::TCP_Info -- cgit v1.2.3-24-ge0c7