kgio RubyGem user+dev discussion/patches/pulls/bugs/help
 help / color / mirror / code / Atom feed
Search results ordered by [date|relevance]  view[summary|nested|Atom feed]
thread overview below | download mbox.gz: |
* [ANN] kgio 2.11.0 - legacy I/O for legacy Ruby + Unix
@ 2016-12-16 23:18  5% Eric Wong
  0 siblings, 0 replies; 5+ results
From: Eric Wong @ 2016-12-16 23:18 UTC (permalink / raw)
  To: kgio-public, ruby-talk

kgio provides non-blocking I/O methods for Ruby without raising
exceptions on EAGAIN and EINPROGRESS.  It is intended for use with the
unicorn Rack server, but may be used by other applications (that run on
Unix-like platforms).

Note: I do not recommend using kgio for future applications, Ruby 2.x
has a lot of the functionality of kgio and Ruby 2.3 has even more.
Keep an eye on the NEWS file for Ruby 2.3+ releases.
However, I'll continue maintaining kgio for legacy purposes
indefinitely.

* homepage: https://bogomips.org/kgio/
* public mailing list: kgio-public@bogomips.org
* git clone git://bogomips.org/kgio.git
* Atom feed https://bogomips.org/kgio/NEWS.atom.xml
* mailing list archives: https://bogomips.org/kgio-public/

Changes: kgio 2.11.0 - reverting 2.10.0 breakage

    Mainly this release reverts back to kgio 2.9.3 behavior by
    supporting "autopush" functionality in the C extension.
    Removing this feature in 2.10.0 was a monumental mistake(*) on
    my part which caused a performance regression for ONE known user
    of kgio on the deprecated (but supported) Rainbows! server.

    This release does not affect known users outside of Rainbows!,
    but we may have unknown users, too.

    The attempt at a pure Ruby version of autopush had flaws and
    regressions in real-world testing, so that was abandoned.

    So yeah, kgio will be supported indefinitely even in the face
    of internal changes to MRI, but I strongly discourage its use
    for new projects.
    Users who only support Ruby 2.3 (and later) are encouraged to
    use `exception: false` with all *_nonblock methods.

    4 changes since 2.10.0:

          resurrect Kgio.autopush support in pure Ruby [REVERTED]
          reinstate the original (and dangerous) autopush in C
          TODO: update with Ruby 2.3 status
          test: increase test data sizes to fill socket buffers

    (*) - as was making this a separate project, at all

^ permalink raw reply	[relevance 5%]

* [PUSHED] reinstate the original (and dangerous) autopush in C
  @ 2016-12-16  0:01  6%   ` Eric Wong
  0 siblings, 0 replies; 5+ results
From: Eric Wong @ 2016-12-16  0:01 UTC (permalink / raw)
  To: kgio-public

The regression for existing users was unnacceptable and
completely poor judgement on my part.  This change brings
us back to potentially not-future-compatible code which
will impose maintenance burdens on us in the face of
future Ruby changes.

But TODAY, it is the most performant option for folks who
need to use autopush.

Revert "resurrect Kgio.autopush support in pure Ruby"
and "remove autopush support and make it a no-op"

This reverts commits 64dc570f4b99f68b5ed792b36e7e8abc3df74927
and 4347980fa66115425fa8b765353c8b1bfe5dec24.
---

Patch is huge and ugly, so not inline:

	https://bogomips.org/kgio.git/patch?id=333347c3ae54c8d6

^ permalink raw reply	[relevance 6%]

* Re: [PATCH v2] resurrect Kgio.autopush support in pure Ruby
  2015-10-09 21:43  2% ` [PATCH v2] " Eric Wong
@ 2015-10-09 21:55  7%   ` Eric Wong
  0 siblings, 0 replies; 5+ results
From: Eric Wong @ 2015-10-09 21:55 UTC (permalink / raw)
  To: kgio-public

Pushed with minor doc updates.

^ permalink raw reply	[relevance 7%]

* [PATCH v2] resurrect Kgio.autopush support in pure Ruby
  2015-10-09  1:01  2% [RFC] resurrect Kgio.autopush support in pure Ruby Eric Wong
@ 2015-10-09 21:43  2% ` Eric Wong
  2015-10-09 21:55  7%   ` Eric Wong
  0 siblings, 1 reply; 5+ results
From: Eric Wong @ 2015-10-09 21:43 UTC (permalink / raw)
  To: kgio-public

This avoids breaking compatibility for existing apps, but
is less performant (although safer and more resilient to future
changes in Ruby) than the previous C version.
---
    Changes since RFC:
    - ensure this is no longer documented or advertised
    - switch from Hash to Array for FDMAP, this is vastly more compact
      for the dense case and improves memory reusability.
    - simplify acceptor logic

 .document                     |   2 +-
 lib/kgio.rb                   |   8 ++-
 lib/kgio/autopush.rb          |  67 ++++++++++++++++++
 lib/kgio/autopush/acceptor.rb |  40 +++++++++++
 lib/kgio/autopush/sock_rw.rb  |  66 ++++++++++++++++++
 test/test_autopush.rb         | 159 +++++++++++++++++++++++++++++++++++++++++-
 6 files changed, 338 insertions(+), 4 deletions(-)
 create mode 100644 lib/kgio/autopush.rb
 create mode 100644 lib/kgio/autopush/acceptor.rb
 create mode 100644 lib/kgio/autopush/sock_rw.rb

diff --git a/.document b/.document
index f3a0872..d00ac56 100644
--- a/.document
+++ b/.document
@@ -5,7 +5,7 @@ NEWS
 LATEST
 ISSUES
 HACKING
-lib
+lib/kgio.rb
 ext/kgio/accept.c
 ext/kgio/connect.c
 ext/kgio/kgio_ext.c
diff --git a/lib/kgio.rb b/lib/kgio.rb
index f192074..5512972 100644
--- a/lib/kgio.rb
+++ b/lib/kgio.rb
@@ -21,10 +21,16 @@ module Kgio
   @autopush = false
 
   class << self
-    attr_accessor :autopush # :nodoc:
+    attr_reader :autopush # :nodoc:
     def autopush? # :nodoc:
       !!@autopush
     end
+
+    def autopush=(bool) # :nodoc:
+      # No require_relative, we remain 1.8-compatible
+      require 'kgio/autopush'
+      @autopush = bool
+    end
   end
 end
 
diff --git a/lib/kgio/autopush.rb b/lib/kgio/autopush.rb
new file mode 100644
index 0000000..72d068f
--- /dev/null
+++ b/lib/kgio/autopush.rb
@@ -0,0 +1,67 @@
+# Copyright (C) 2015 all contributors <kgio-public@bogomips.org>
+# License: LGPLv2.1 or later (https://www.gnu.org/licenses/lgpl-2.1.txt)
+
+require 'socket'
+require 'thread'
+
+module Kgio::Autopush # :nodoc:
+  class SyncArray # :nodoc:
+    def initialize # :nodoc:
+      @map = []
+      @lock = Mutex.new
+    end
+
+    def []=(key, val) # :nodoc:
+      @lock.synchronize { @map[key] = val }
+    end
+
+    def [](key) # :nodoc:
+      @lock.synchronize { @map[key] }
+    end
+  end
+
+  FDMAP = SyncArray.new # :nodoc:
+  APState = Struct.new(:obj, :ap_state) # :nodoc:
+
+  # Not using pre-defined socket constants for 1.8 compatibility
+  if RUBY_PLATFORM.include?('linux')
+    NOPUSH = 3 # :nodoc:
+  elsif RUBY_PLATFORM.include?('freebsd')
+    NOPUSH = 4 # :nodoc:
+  end
+
+  def kgio_autopush? # :nodoc:
+    return false unless Kgio.autopush?
+    state = FDMAP[fileno]
+    state && state.obj == self && state.ap_state != :ignore
+  end
+
+  def kgio_autopush=(bool) # :nodoc:
+    if bool
+      state = FDMAP[fileno] ||= APState.new
+      state.ap_state = :writer
+      state.obj = self
+    end
+    bool
+  end
+
+private
+  def kgio_push_pending # :nodoc:
+    Kgio.autopush or return
+    state = FDMAP[fileno] or return
+    state.obj == self and state.ap_state = :written
+  end
+
+  def kgio_push_pending_data # :nodoc:
+    Kgio.autopush or return
+    state = FDMAP[fileno] or return
+    state.obj == self && state.ap_state == :written or return
+    setsockopt(Socket::IPPROTO_TCP, NOPUSH, 0)
+    setsockopt(Socket::IPPROTO_TCP, NOPUSH, 1)
+    state.ap_state = :writer
+  end
+end
+require 'kgio/autopush/sock_rw'
+require 'kgio/autopush/acceptor'
+Kgio::TCPSocket.__send__(:include, Kgio::Autopush::SockRW) # :nodoc:
+Kgio::Socket.__send__(:include, Kgio::Autopush::SockRW) # :nodoc:
diff --git a/lib/kgio/autopush/acceptor.rb b/lib/kgio/autopush/acceptor.rb
new file mode 100644
index 0000000..37ae163
--- /dev/null
+++ b/lib/kgio/autopush/acceptor.rb
@@ -0,0 +1,40 @@
+# Copyright (C) 2015 all contributors <kgio-public@bogomips.org>
+# License: LGPLv2.1 or later (https://www.gnu.org/licenses/lgpl-2.1.txt)
+class Kgio::TCPServer
+  include Kgio::Autopush
+
+  alias_method :kgio_accept_orig, :kgio_accept
+  undef_method :kgio_accept
+  def kgio_accept(*args)
+    kgio_autopush_post_accept(kgio_accept_orig(*args))
+  end
+
+  alias_method :kgio_tryaccept_orig, :kgio_tryaccept
+  undef_method :kgio_tryaccept
+  def kgio_tryaccept(*args)
+    kgio_autopush_post_accept(kgio_tryaccept_orig(*args))
+  end
+
+private
+
+  def kgio_autopush_post_accept(rv) # :nodoc:
+    return rv unless Kgio.autopush? && rv.respond_to?(:kgio_autopush=)
+    if my_state = FDMAP[fileno]
+      if my_state.obj == self
+        rv.kgio_autopush = true if my_state.ap_state == :acceptor
+        return rv
+      end
+    else
+      my_state = FDMAP[fileno] ||= Kgio::Autopush::APState.new
+    end
+    my_state.obj = self
+    my_state.ap_state = nil
+    begin
+      n = getsockopt(Socket::IPPROTO_TCP, Kgio::Autopush::NOPUSH).unpack('i')
+      my_state.ap_state = :acceptor if n[0] == 1
+    rescue Errno::ENOTSUPP # non-TCP socket
+    end
+    rv.kgio_autopush = true if my_state.ap_state == :acceptor
+    rv
+  end
+end
diff --git a/lib/kgio/autopush/sock_rw.rb b/lib/kgio/autopush/sock_rw.rb
new file mode 100644
index 0000000..41b11ce
--- /dev/null
+++ b/lib/kgio/autopush/sock_rw.rb
@@ -0,0 +1,66 @@
+# Copyright (C) 2015 all contributors <kgio-public@bogomips.org>
+# License: LGPLv2.1 or later (https://www.gnu.org/licenses/lgpl-2.1.txt)
+module Kgio::Autopush::SockRW # :nodoc:
+  include Kgio::Autopush
+
+  def kgio_read(*) # :nodoc:
+    kgio_push_pending_data
+    super
+  end
+
+  def kgio_read!(*) # :nodoc:
+    kgio_push_pending_data
+    super
+  end
+
+  def kgio_tryread(*) # :nodoc:
+    kgio_push_pending_data
+    super
+  end
+
+  def kgio_trypeek(*) # :nodoc:
+    kgio_push_pending_data
+    super
+  end
+
+  def kgio_peek(*) # :nodoc:
+    kgio_push_pending_data
+    super
+  end
+
+  def kgio_syssend(*) # :nodoc:
+    kgio_push_pending_data(super)
+  end if Kgio::SocketMethods.method_defined?(:kgio_syssend)
+
+  def kgio_trysend(*) # :nodoc:
+    kgio_ap_wrap_writer(super)
+  end
+
+  def kgio_trywrite(*) # :nodoc:
+    kgio_ap_wrap_writer(super)
+  end
+
+  def kgio_trywritev(*) # :nodoc:
+    kgio_ap_wrap_writer(super)
+  end
+
+  def kgio_write(*) # :nodoc:
+    kgio_ap_wrap_writer(super)
+  end
+
+  def kgio_writev(*) # :nodoc:
+    kgio_ap_wrap_writer(super)
+  end
+
+private
+
+  def kgio_ap_wrap_writer(rv) # :nodoc:
+    case rv
+    when :wait_readable, :wait_writable
+      kgio_push_pending_data
+    else
+      kgio_push_pending
+    end
+    rv
+  end
+end
diff --git a/test/test_autopush.rb b/test/test_autopush.rb
index 4e5af92..d2ae830 100644
--- a/test/test_autopush.rb
+++ b/test/test_autopush.rb
@@ -1,11 +1,166 @@
+require 'tempfile'
 require 'test/unit'
+begin
+  $-w = false
+  RUBY_PLATFORM =~ /linux/ and require 'strace'
+rescue LoadError
+end
+$-w = true
 require 'kgio'
 
 class TestAutopush < Test::Unit::TestCase
-  def test_compatibility
+  TCP_CORK = 3
+  TCP_NOPUSH = 4
+
+  def setup
+    Kgio.autopush = false
+    assert_equal false, Kgio.autopush?
+
+    @host = ENV["TEST_HOST"] || '127.0.0.1'
+    @srv = Kgio::TCPServer.new(@host, 0)
+    RUBY_PLATFORM =~ /linux/ and
+      @srv.setsockopt(Socket::IPPROTO_TCP, TCP_CORK, 1)
+    RUBY_PLATFORM =~ /freebsd/ and
+      @srv.setsockopt(Socket::IPPROTO_TCP, TCP_NOPUSH, 1)
+    @port = @srv.addr[1]
+  end
+
+  def test_autopush_accessors
+    Kgio.autopush = true
+    opt = RUBY_PLATFORM =~ /freebsd/ ? TCP_NOPUSH : TCP_CORK
+    s = Kgio::TCPSocket.new(@host, @port)
+    assert_equal 0, s.getsockopt(Socket::IPPROTO_TCP, opt).unpack('i')[0]
+    assert ! s.kgio_autopush?
+    s.kgio_autopush = true
+    assert s.kgio_autopush?
+    s.kgio_write 'asdf'
+    assert_equal :wait_readable, s.kgio_tryread(1)
+    assert s.kgio_autopush?
+    val = s.getsockopt(Socket::IPPROTO_TCP, opt).unpack('i')[0]
+    assert_operator val, :>, 0, "#{opt}=#{val} (#{RUBY_PLATFORM})"
+  end
+
+  def test_autopush_true_unix
+    Kgio.autopush = true
+    tmp = Tempfile.new('kgio_unix')
+    @path = tmp.path
+    tmp.close!
+    @srv = Kgio::UNIXServer.new(@path)
+    @rd = Kgio::UNIXSocket.new(@path)
+    t0 = nil
+    if defined?(Strace)
+      io, err = Strace.me { @wr = @srv.kgio_accept }
+      assert_nil err
+      rc = nil
+      io, err = Strace.me {
+        t0 = Time.now
+        @wr.kgio_write "HI\n"
+        rc = @wr.kgio_tryread 666
+      }
+      assert_nil err
+      lines = io.readlines
+      assert lines.grep(/TCP_CORK/).empty?, lines.inspect
+    else
+      @wr = @srv.kgio_accept
+      t0 = Time.now
+      @wr.kgio_write "HI\n"
+      rc = @wr.kgio_tryread 666
+    end
+    assert_equal "HI\n", @rd.kgio_read(3)
+    diff = Time.now - t0
+    assert(diff < 0.200, "nopush on UNIX sockets? diff=#{diff} > 200ms")
+    assert_equal :wait_readable, rc
+  ensure
+    File.unlink(@path) rescue nil
+  end
+
+  def test_autopush_false
+    Kgio.autopush = nil
+    assert_equal false, Kgio.autopush?
+
+    @wr = Kgio::TCPSocket.new(@host, @port)
+    if defined?(Strace)
+      io, err = Strace.me { @rd = @srv.kgio_accept }
+      assert_nil err
+      lines = io.readlines
+      assert lines.grep(/TCP_CORK/).empty?, lines.inspect
+      assert_equal 1, @rd.getsockopt(Socket::SOL_TCP, TCP_CORK).unpack("i")[0]
+    else
+      @rd = @srv.kgio_accept
+    end
+
+    rbuf = "..."
+    t0 = Time.now
+    @rd.kgio_write "HI\n"
+    @wr.kgio_read(3, rbuf)
+    diff = Time.now - t0
+    assert(diff >= 0.190, "nopush broken? diff=#{diff} > 200ms")
+    assert_equal "HI\n", rbuf
+  end
+
+  def test_autopush_true
     Kgio.autopush = true
     assert_equal true, Kgio.autopush?
+    @wr = Kgio::TCPSocket.new(@host, @port)
+
+    if defined?(Strace)
+      io, err = Strace.me { @rd = @srv.kgio_accept }
+      assert_nil err
+      lines = io.readlines
+      assert_equal 1, lines.grep(/TCP_CORK/).size, lines.inspect
+      assert_equal 1, @rd.getsockopt(Socket::SOL_TCP, TCP_CORK).unpack("i")[0]
+    else
+      @rd = @srv.kgio_accept
+    end
+
+    @wr.write "HI\n"
+    rbuf = ""
+    if defined?(Strace)
+      io, err = Strace.me { @rd.kgio_read(3, rbuf) }
+      assert_nil err
+      lines = io.readlines
+      assert lines.grep(/TCP_CORK/).empty?, lines.inspect
+      assert_equal "HI\n", rbuf
+    else
+      assert_equal "HI\n", @rd.kgio_read(3, rbuf)
+    end
+
+    t0 = Time.now
+    @rd.kgio_write "HI2U2\n"
+    @rd.kgio_write "HOW\n"
+    rc = false
+
+    if defined?(Strace)
+      io, err = Strace.me { rc = @rd.kgio_tryread(666) }
+    else
+      rc = @rd.kgio_tryread(666)
+    end
+
+    @wr.readpartial(666, rbuf)
+    rbuf == "HI2U2\nHOW\n" or warn "rbuf=#{rbuf.inspect} looking bad?"
+    diff = Time.now - t0
+    assert(diff < 0.200, "time diff=#{diff} >= 200ms")
+    assert_equal :wait_readable, rc
+    if defined?(Strace)
+      assert_nil err
+      lines = io.readlines
+      assert_equal 2, lines.grep(/TCP_CORK/).size, lines.inspect
+    end
+    @wr.close
+    @rd.close
+
+    @wr = Kgio::TCPSocket.new(@host, @port)
+    if defined?(Strace)
+      io, err = Strace.me { @rd = @srv.kgio_accept }
+      assert_nil err
+      lines = io.readlines
+      assert lines.grep(/TCP_CORK/).empty?,"optimization fail: #{lines.inspect}"
+      assert_equal 1, @rd.getsockopt(Socket::SOL_TCP, TCP_CORK).unpack("i")[0]
+    end
+  end
+
+  def teardown
     Kgio.autopush = false
     assert_equal false, Kgio.autopush?
   end
-end
+end if RUBY_PLATFORM =~ /linux|freebsd/
-- 
EW


^ permalink raw reply related	[relevance 2%]

* [RFC] resurrect Kgio.autopush support in pure Ruby
@ 2015-10-09  1:01  2% Eric Wong
  2015-10-09 21:43  2% ` [PATCH v2] " Eric Wong
  0 siblings, 1 reply; 5+ results
From: Eric Wong @ 2015-10-09  1:01 UTC (permalink / raw)
  To: kgio-public

This avoids breaking compatibility for existing apps, but
is less performant (although safer and more resilient to future
changes in Ruby) than the previous C version.
---
 lib/kgio.rb                   |   8 ++-
 lib/kgio/autopush.rb          |  72 +++++++++++++++++++
 lib/kgio/autopush/acceptor.rb |  40 +++++++++++
 lib/kgio/autopush/sock_rw.rb  |  62 ++++++++++++++++
 test/test_autopush.rb         | 160 +++++++++++++++++++++++++++++++++++++++++-
 5 files changed, 338 insertions(+), 4 deletions(-)
 create mode 100644 lib/kgio/autopush.rb
 create mode 100644 lib/kgio/autopush/acceptor.rb
 create mode 100644 lib/kgio/autopush/sock_rw.rb

diff --git a/lib/kgio.rb b/lib/kgio.rb
index f192074..5512972 100644
--- a/lib/kgio.rb
+++ b/lib/kgio.rb
@@ -21,10 +21,16 @@ module Kgio
   @autopush = false
 
   class << self
-    attr_accessor :autopush # :nodoc:
+    attr_reader :autopush # :nodoc:
     def autopush? # :nodoc:
       !!@autopush
     end
+
+    def autopush=(bool) # :nodoc:
+      # No require_relative, we remain 1.8-compatible
+      require 'kgio/autopush'
+      @autopush = bool
+    end
   end
 end
 
diff --git a/lib/kgio/autopush.rb b/lib/kgio/autopush.rb
new file mode 100644
index 0000000..120d237
--- /dev/null
+++ b/lib/kgio/autopush.rb
@@ -0,0 +1,72 @@
+require 'socket'
+require 'thread'
+
+module Kgio::Autopush # :nodoc:
+  class SyncHash
+    def initialize
+      @map = {}
+      @lock = Mutex.new
+    end
+
+    def []=(key, val)
+      @lock.synchronize { @map[key] = val }
+    end
+
+    def [](key)
+      @lock.synchronize { @map[key] }
+    end
+
+    def delete(key)
+      @lock.synchronize { @map.delete(key) }
+    end
+  end
+
+  FDMAP = SyncHash.new # :nodoc:
+  APState = Struct.new(:obj, :ap_state)
+
+  # Not using pre-defined socket constants for 1.8 compatibility
+  if RUBY_PLATFORM.include?('linux')
+    NOPUSH = 3
+  elsif RUBY_PLATFORM.include?('freebsd')
+    NOPUSH = 4
+  end
+
+  def kgio_push_pending
+    Kgio.autopush or return
+    state = FDMAP[fileno] or return
+    state.obj == self and state.ap_state = :written
+  end
+
+  def kgio_push_pending_data # :nodoc:
+    Kgio.autopush or return
+    state = FDMAP[fileno] or return
+    state.obj == self && state.ap_state == :written or return
+    setsockopt(Socket::IPPROTO_TCP, NOPUSH, 0)
+    setsockopt(Socket::IPPROTO_TCP, NOPUSH, 1)
+    state.ap_state = :writer
+  rescue # ignore socket errors
+    FDMAP.delete(fileno)
+  end
+
+  def kgio_autopush?
+    return false unless Kgio.autopush?
+    state = FDMAP[fileno]
+    state && state.obj == self && state.ap_state != :ignore
+  end
+
+  def kgio_autopush=(bool)
+    if bool
+      state = FDMAP[fileno] ||= APState.new
+      state.ap_state = :writer
+      state.obj = self
+    else
+      FDMAP.delete(fileno)
+    end
+    bool
+  end
+end
+require 'kgio/autopush/sock_rw'
+require 'kgio/autopush/acceptor'
+Kgio::TCPSocket.__send__(:include, Kgio::Autopush::SockRW)
+Kgio::Socket.__send__(:include, Kgio::Autopush::SockRW)
+# Kgio::TCPServer.__send__(:include, Kgio::Autopush::Acceptor)
diff --git a/lib/kgio/autopush/acceptor.rb b/lib/kgio/autopush/acceptor.rb
new file mode 100644
index 0000000..856891f
--- /dev/null
+++ b/lib/kgio/autopush/acceptor.rb
@@ -0,0 +1,40 @@
+# module Kgio::Autopush::Acceptor # :nodoc:
+class Kgio::TCPServer
+  include Kgio::Autopush
+  alias_method :kgio_accept_orig, :kgio_accept
+  undef_method :kgio_accept
+  def kgio_accept(*args) # :nodoc:
+    kgio_autopush_post_accept(kgio_accept_orig(*args))
+  end
+
+  alias_method :kgio_tryaccept_orig, :kgio_tryaccept
+  undef_method :kgio_tryaccept
+  def kgio_tryaccept(*args) # :nodoc:
+    kgio_autopush_post_accept(kgio_tryaccept_orig(*args))
+  end
+
+private
+
+  def kgio_autopush_post_accept(rv) # :nodoc:
+    return rv unless Kgio.autopush? && rv.respond_to?(:kgio_autopush=)
+    if my_state = FDMAP[fileno]
+      if my_state.obj == self && my_state.ap_state == :acceptor
+        rv.kgio_autopush = true if rv.respond_to?(:kgio_autopush=)
+      end
+      return rv
+    else
+      my_state = FDMAP[fileno] ||= Kgio::Autopush::APState.new
+    end
+    my_state.obj = self
+    my_state.ap_state = nil
+    begin
+      n = getsockopt(Socket::IPPROTO_TCP, Kgio::Autopush::NOPUSH).unpack('i')
+      my_state.ap_state = :acceptor if n[0] == 1
+    rescue Errno::ENOTSUPP # non-TCP socket
+    end
+    return rv unless my_state.ap_state == :acceptor
+
+    rv.kgio_autopush = true if rv.respond_to?(:kgio_autopush=)
+    rv
+  end
+end
diff --git a/lib/kgio/autopush/sock_rw.rb b/lib/kgio/autopush/sock_rw.rb
new file mode 100644
index 0000000..62e4b65
--- /dev/null
+++ b/lib/kgio/autopush/sock_rw.rb
@@ -0,0 +1,62 @@
+module Kgio::Autopush::SockRW
+  include Kgio::Autopush
+
+  def kgio_read(*) # :nodoc:
+    kgio_push_pending_data
+    super
+  end
+
+  def kgio_read!(*) # :nodoc:
+    kgio_push_pending_data
+    super
+  end
+
+  def kgio_tryread(*) # :nodoc:
+    kgio_push_pending_data
+    super
+  end
+
+  def kgio_trypeek(*) # :nodoc:
+    kgio_push_pending_data
+    super
+  end
+
+  def kgio_peek(*) # :nodoc:
+    kgio_push_pending_data
+    super
+  end
+
+  def kgio_syssend(*) # :nodoc:
+    kgio_push_pending_data(super)
+  end if Kgio::SocketMethods.method_defined?(:kgio_syssend)
+
+  def kgio_trysend(*) # :nodoc:
+    kgio_ap_wrap_writer(super)
+  end
+
+  def kgio_trywrite(*) # :nodoc:
+    kgio_ap_wrap_writer(super)
+  end
+
+  def kgio_trywritev(*) # :nodoc:
+    kgio_ap_wrap_writer(super)
+  end
+
+  def kgio_write(*) # :nodoc:
+    kgio_ap_wrap_writer(super)
+  end
+
+  def kgio_writev(*) # :nodoc:
+    kgio_ap_wrap_writer(super)
+  end
+
+  def kgio_ap_wrap_writer(rv) # :nodoc:
+    case rv
+    when :wait_readable, :wait_writable
+      kgio_push_pending_data
+    else
+      kgio_push_pending
+    end
+    rv
+  end
+end
diff --git a/test/test_autopush.rb b/test/test_autopush.rb
index 4e5af92..38b7c52 100644
--- a/test/test_autopush.rb
+++ b/test/test_autopush.rb
@@ -1,11 +1,165 @@
+require 'tempfile'
 require 'test/unit'
+begin
+  $-w = false
+  RUBY_PLATFORM =~ /linux/ and require 'strace'
+rescue LoadError
+end
+$-w = true
 require 'kgio'
 
 class TestAutopush < Test::Unit::TestCase
-  def test_compatibility
+  TCP_CORK = 3
+  TCP_NOPUSH = 4
+
+  def setup
+    Kgio.autopush = false
+    assert_equal false, Kgio.autopush?
+
+    @host = ENV["TEST_HOST"] || '127.0.0.1'
+    @srv = Kgio::TCPServer.new(@host, 0)
+    RUBY_PLATFORM =~ /linux/ and
+      @srv.setsockopt(Socket::IPPROTO_TCP, TCP_CORK, 1)
+    RUBY_PLATFORM =~ /freebsd/ and
+      @srv.setsockopt(Socket::IPPROTO_TCP, TCP_NOPUSH, 1)
+    @port = @srv.addr[1]
+  end
+
+  def test_autopush_accessors
+    Kgio.autopush = true
+    opt = RUBY_PLATFORM =~ /freebsd/ ? TCP_NOPUSH : TCP_CORK
+    s = Kgio::TCPSocket.new(@host, @port)
+    assert_equal 0, s.getsockopt(Socket::IPPROTO_TCP, opt).unpack('i')[0]
+    assert ! s.kgio_autopush?
+    s.kgio_autopush = true
+    assert s.kgio_autopush?
+    s.kgio_write 'asdf'
+    assert_equal :wait_readable, s.kgio_tryread(1)
+    assert s.kgio_autopush?
+    val = s.getsockopt(Socket::IPPROTO_TCP, opt).unpack('i')[0]
+    assert_operator val, :>, 0, "#{opt}=#{val} (#{RUBY_PLATFORM})"
+  end
+
+  def test_autopush_true_unix
+    Kgio.autopush = true
+    tmp = Tempfile.new('kgio_unix')
+    @path = tmp.path
+    tmp.close!
+    @srv = Kgio::UNIXServer.new(@path)
+    @rd = Kgio::UNIXSocket.new(@path)
+    t0 = nil
+    if defined?(Strace)
+      io, err = Strace.me { @wr = @srv.kgio_accept }
+      assert_nil err
+      rc = nil
+      io, err = Strace.me {
+        t0 = Time.now
+        @wr.kgio_write "HI\n"
+        rc = @wr.kgio_tryread 666
+      }
+      assert_nil err
+      lines = io.readlines
+      assert lines.grep(/TCP_CORK/).empty?, lines.inspect
+    else
+      @wr = @srv.kgio_accept
+      t0 = Time.now
+      @wr.kgio_write "HI\n"
+      rc = @wr.kgio_tryread 666
+    end
+    assert_equal "HI\n", @rd.kgio_read(3)
+    diff = Time.now - t0
+    assert(diff < 0.200, "nopush on UNIX sockets? diff=#{diff} > 200ms")
+    assert_equal :wait_readable, rc
+  ensure
+    File.unlink(@path) rescue nil
+  end
+
+  def test_autopush_false
+    Kgio.autopush = nil
+    assert_equal false, Kgio.autopush?
+
+    @wr = Kgio::TCPSocket.new(@host, @port)
+    if defined?(Strace)
+      io, err = Strace.me { @rd = @srv.kgio_accept }
+      assert_nil err
+      lines = io.readlines
+      assert lines.grep(/TCP_CORK/).empty?, lines.inspect
+      assert_equal 1, @rd.getsockopt(Socket::SOL_TCP, TCP_CORK).unpack("i")[0]
+    else
+      @rd = @srv.kgio_accept
+    end
+
+    rbuf = "..."
+    t0 = Time.now
+    @rd.kgio_write "HI\n"
+    @wr.kgio_read(3, rbuf)
+    diff = Time.now - t0
+    assert(diff >= 0.190, "nopush broken? diff=#{diff} > 200ms")
+    assert_equal "HI\n", rbuf
+  end
+
+  def test_autopush_true
     Kgio.autopush = true
     assert_equal true, Kgio.autopush?
+    @wr = Kgio::TCPSocket.new(@host, @port)
+
+    if defined?(Strace)
+      io, err = Strace.me { @rd = @srv.kgio_accept }
+      assert_nil err
+      lines = io.readlines
+      assert_equal 1, lines.grep(/TCP_CORK/).size, lines.inspect
+      assert_equal 1, @rd.getsockopt(Socket::SOL_TCP, TCP_CORK).unpack("i")[0]
+    else
+      @rd = @srv.kgio_accept
+    end
+
+    @wr.write "HI\n"
+    rbuf = ""
+    if defined?(Strace)
+      io, err = Strace.me { @rd.kgio_read(3, rbuf) }
+      assert_nil err
+      lines = io.readlines
+      assert lines.grep(/TCP_CORK/).empty?, lines.inspect
+      assert_equal "HI\n", rbuf
+    else
+      assert_equal "HI\n", @rd.kgio_read(3, rbuf)
+    end
+
+    t0 = Time.now
+    @rd.kgio_write "HI2U2\n"
+    @rd.kgio_write "HOW\n"
+    rc = false
+
+    if defined?(Strace)
+      io, err = Strace.me { rc = @rd.kgio_tryread(666) }
+    else
+      rc = @rd.kgio_tryread(666)
+    end
+
+    @wr.readpartial(666, rbuf)
+    rbuf == "HI2U2\nHOW\n" or warn "rbuf=#{rbuf.inspect} looking bad?"
+    diff = Time.now - t0
+    assert(diff < 0.200, "time diff=#{diff} >= 200ms")
+    assert_equal :wait_readable, rc
+    if defined?(Strace)
+      assert_nil err
+      lines = io.readlines
+      assert_equal 2, lines.grep(/TCP_CORK/).size, lines.inspect
+    end
+    @wr.close
+    @rd.close
+
+    @wr = Kgio::TCPSocket.new(@host, @port)
+    if defined?(Strace)
+      io, err = Strace.me { @rd = @srv.kgio_accept }
+      assert_nil err
+      lines = io.readlines
+      assert lines.grep(/TCP_CORK/).empty?,"optimization fail: #{lines.inspect}"
+      assert_equal 1, @rd.getsockopt(Socket::SOL_TCP, TCP_CORK).unpack("i")[0]
+    end
+  end
+
+  def teardown
     Kgio.autopush = false
-    assert_equal false, Kgio.autopush?
   end
-end
+end if RUBY_PLATFORM =~ /linux|freebsd/
-- 
EW


^ permalink raw reply related	[relevance 2%]

Results 1-5 of 5 | reverse | options above
-- pct% links below jump to the message on this page, permalinks otherwise --
2015-08-13 20:55     [PATCH] remove autopush support and make it a no-op Eric Wong
2016-12-15 22:50     ` Eric Wong
2016-12-16  0:01  6%   ` [PUSHED] reinstate the original (and dangerous) autopush in C Eric Wong
2015-10-09  1:01  2% [RFC] resurrect Kgio.autopush support in pure Ruby Eric Wong
2015-10-09 21:43  2% ` [PATCH v2] " Eric Wong
2015-10-09 21:55  7%   ` Eric Wong
2016-12-16 23:18  5% [ANN] kgio 2.11.0 - legacy I/O for legacy Ruby + Unix Eric Wong

Code repositories for project(s) associated with this public inbox

	https://yhbt.net/kgio.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).