http_spew RubyGem user+dev discussion/patches/pulls/bugs/help
 help / color / mirror / code / Atom feed
* [PATCH] remove most uses of kgio
@ 2017-03-09 20:43 Eric Wong
  2017-03-10  0:44 ` [PATCH 2/1] make kgio entirely optional Eric Wong
  0 siblings, 1 reply; 2+ messages in thread
From: Eric Wong @ 2017-03-09 20:43 UTC (permalink / raw)
  To: http_spew-public

And use Ruby 2.3+ *_nonblock(... exception: false) instead.
Since most of kgio-specific features are in newer releases
of Ruby, we can start phasing kgio out to avoid hassling
users with installing more software.

We can still benefit from Kgio.poll over IO.select at
the moment, but we can make it optional in the future.
---
 http_spew.gemspec                |  2 +-
 lib/http_spew.rb                 |  1 +
 lib/http_spew/chunky_pipe.rb     | 17 +++++++++---
 lib/http_spew/class_methods.rb   |  4 +--
 lib/http_spew/request.rb         | 56 ++++++++++++++++++++++++++--------------
 test/test_hit_n_run.rb           |  8 +++---
 test/test_request.rb             | 10 +++----
 test/test_unexpected_response.rb |  2 +-
 8 files changed, 64 insertions(+), 36 deletions(-)

diff --git a/http_spew.gemspec b/http_spew.gemspec
index 4bede9b..d4d38da 100644
--- a/http_spew.gemspec
+++ b/http_spew.gemspec
@@ -18,6 +18,6 @@ Gem::Specification.new do |s|
   s.add_dependency(%q<kcar>, [ "~> 0.3", ">= 0.3.1"])
   s.add_dependency(%q<kgio>, "~> 2.6")
   s.add_development_dependency(%q<olddoc>, "~> 1.0")
-  s.required_ruby_version = '>= 2.1'
+  s.required_ruby_version = '>= 2.3'
   s.licenses = %w(GPL-2.0+)
 end
diff --git a/lib/http_spew.rb b/lib/http_spew.rb
index e6bde3b..b658627 100644
--- a/lib/http_spew.rb
+++ b/lib/http_spew.rb
@@ -1,4 +1,5 @@
 # -*- encoding: binary -*-
+require "io/wait"
 require "kgio"
 require "kcar"
 
diff --git a/lib/http_spew/chunky_pipe.rb b/lib/http_spew/chunky_pipe.rb
index 4c51663..590d2f1 100644
--- a/lib/http_spew/chunky_pipe.rb
+++ b/lib/http_spew/chunky_pipe.rb
@@ -3,17 +3,28 @@
 # This is a OS-level pipe that overrides IO#read to provide
 # IO#readpartial-like semantics while remaining Rack::Lint-compatible
 # for EOF, meaning we return nil on EOF instead of raising EOFError.
-class HTTP_Spew::ChunkyPipe < Kgio::Pipe
+class HTTP_Spew::ChunkyPipe < IO
 
   # other threads may force an error to be raised in the +read+
   # method
   attr_accessor :error
 
+  class << self
+    alias new pipe
+  end
+
   # Override IO#read to behave like IO#readpartial, but still return +nil+
   # on EOF instead of raising EOFError.
-  def read(*args)
+  def read(len = 16384, buf = '')
     check_err!
-    kgio_read(*args) || check_err! || close
+    case read_nonblock(len, buf, exception: false)
+    when nil
+      return check_err! || close
+    when :wait_readable
+      wait_readable # retry
+    else
+      return buf
+    end while true
   end
 
   def check_err!
diff --git a/lib/http_spew/class_methods.rb b/lib/http_spew/class_methods.rb
index f068f6b..79b3e47 100644
--- a/lib/http_spew/class_methods.rb
+++ b/lib/http_spew/class_methods.rb
@@ -53,7 +53,7 @@ module HTTP_Spew::ClassMethods
   # If +need+ is fullfilled, it closes all incomplete requests.
   def wait_mt(need, requests, timeout)
     ready, failed = [], []
-    r, w = Kgio::Pipe.new
+    r, w = IO.pipe
     active = []
     t = [ timeout ]
     requests.each_with_index do |req, i|
@@ -68,7 +68,7 @@ module HTTP_Spew::ClassMethods
       end
     end
     begin
-      with_timeout(t) { r.kgio_wait_readable(t[0]) }
+      with_timeout(t) { r.wait_readable(t[0]) }
       req_idx = r.read(2).unpack("v".freeze)[0]
       thr = active[req_idx]
       with_timeout(t) { thr.join(t[0]) }
diff --git a/lib/http_spew/request.rb b/lib/http_spew/request.rb
index 1d02a98..1647d23 100644
--- a/lib/http_spew/request.rb
+++ b/lib/http_spew/request.rb
@@ -1,4 +1,6 @@
 # -*- encoding: binary -*-
+require 'socket'
+
 # This is the base class actually capable of making a normal HTTP request
 class HTTP_Spew::Request
 
@@ -21,10 +23,9 @@ class HTTP_Spew::Request
   #
   # +sock+ may be the String representing an address created with
   # +Socket.pack_sockaddr_un+ or +Socket.pack_sockaddr_in+, or it
-  # may be an actual IO object with Kgio::SocketMethods mixed in
-  # (e.g. Kgio::Socket)
+  # may be an actual Socket object
   def initialize(env, input, sock, allow = nil)
-    @to_io = Kgio::SocketMethods === sock ? sock : Kgio::Socket.start(sock)
+    @to_io = BasicSocket === sock ? sock : start_sock(sock)
     if Hash === env
       @buf, @input = env_to_headers(env, input)
     else
@@ -37,18 +38,21 @@ class HTTP_Spew::Request
   # returns :wait_readable or :wait_writable if busy
   def resume
     if @buf
-      case rv = @to_io.kgio_trywrite(@buf)
-      when String # unlikely
-        @buf = rv # loop retry, socket buffer could've expanded
-      when Symbol
-        return rv
-      else # done writing, read more
-        @buf = @input ? @input.read(0x4000, @buf) : nil
+      case w = @to_io.write_nonblock(@buf, exception: false)
+      when :wait_writable, :wait_readable
+        return w
+      else # Integer
+        len = @buf.size
+        if w == len
+          @buf = @input ? @input.read(0x4000, @buf) : nil
+        else
+          tmp = @buf.byteslice(w, len - w)
+          @buf.clear
+          @buf = tmp # loop retry, socket buffer could've expanded
+        end
       end while @buf
-      read_response
-    else
-      read_response
     end
+    read_response
   end
 
   # returns a 3-element Rack response array on successful completion
@@ -64,7 +68,7 @@ class HTTP_Spew::Request
     timeout -= (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0)
     while :wait_readable == (rv = read_response) && timeout >= 0.0
       t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
-      @to_io.kgio_wait_readable(timeout) if timeout > 0.0
+      @to_io.wait_readable(timeout) if timeout > 0.0
       timeout -= (Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0)
     end
     rv
@@ -78,7 +82,7 @@ class HTTP_Spew::Request
   # Users do not need to call this directly, +resume+ will return the result
   # of this.
   def read_response
-    buf = @to_io.kgio_trypeek(0x4000) or
+    buf = @to_io.recv_nonblock(0x4000, Socket::MSG_PEEK, exception: false) or
       raise HttpSpew::EOF, "upstream server closed connection", []
     String === buf or return buf
 
@@ -91,7 +95,7 @@ class HTTP_Spew::Request
     end
 
     # discard the header data from the socket buffer
-    (hdr_len -= buf.size) > 0 and @to_io.kgio_read(hdr_len, buf)
+    (hdr_len -= buf.size) > 0 and @to_io.read(hdr_len, buf)
     @response = r << self
   end
 
@@ -102,10 +106,15 @@ class HTTP_Spew::Request
   # Called by Rack servers to write the response to a client
   def each
     buf = ""
-    while @to_io.kgio_read(0x4000, buf)
+    case @to_io.read_nonblock(0x4000, buf, exception: false)
+    when :wait_readable
+      @to_io.wait_readable
+    when nil
+      buf.clear
+      return
+    else
       yield buf
-    end
-    buf.clear
+    end while true
   end
 
   # Used internally by various HTTP_Spew elements to report errors
@@ -121,4 +130,13 @@ class HTTP_Spew::Request
     @to_io.close
     @input = nil
   end
+
+  def start_sock(ai)
+    ai = Addrinfo.new(ai) unless Addrinfo === ai
+    sock = Socket.new(ai.afamily, :SOCK_STREAM)
+    case sock.connect_nonblock(ai, exception: false)
+    when 0, :wait_writable
+    end
+    sock
+  end
 end
diff --git a/test/test_hit_n_run.rb b/test/test_hit_n_run.rb
index 751a8bf..425f2d5 100644
--- a/test/test_hit_n_run.rb
+++ b/test/test_hit_n_run.rb
@@ -19,7 +19,8 @@ class TestHitNRun < Test::Unit::TestCase
   end
 
   def test_request_with_existing_socket
-    sock = Kgio::Socket.new(@sockaddr)
+    sock = TCPSocket.new(@addr, @port)
+    assert(BasicSocket === sock)
     req = HTTP_Spew::HitNRun.new(@env, nil, sock)
     assert_equal sock, req.to_io
     assert_nothing_raised { req.close }
@@ -30,8 +31,7 @@ class TestHitNRun < Test::Unit::TestCase
     req = HTTP_Spew::HitNRun.new(@env, nil, @sockaddr)
     sym = req.resume
     if sym == :wait_writable
-      set = Kgio.poll({req => sym}, 100)
-      assert_equal [ req ], set.keys
+      assert req.to_io.wait_writable(0.1)
       sym = req.resume
     end
     assert_equal HTTP_Spew::HitNRun::RESPONSE.object_id, sym.object_id
@@ -40,7 +40,7 @@ class TestHitNRun < Test::Unit::TestCase
   def test_request_loop
     req = HTTP_Spew::HitNRun.new(@env, nil, @sockaddr)
     until Array === (rv = req.resume)
-      Kgio.poll(req => rv)
+      req.__send__(rv)
     end
     assert_equal HTTP_Spew::HitNRun::RESPONSE.object_id, rv.object_id
   end
diff --git a/test/test_request.rb b/test/test_request.rb
index 67ad70c..b8165c4 100644
--- a/test/test_request.rb
+++ b/test/test_request.rb
@@ -19,7 +19,7 @@ class TestRequest < Test::Unit::TestCase
   end
 
   def test_request_with_existing_socket
-    sock = Kgio::Socket.new(@sockaddr)
+    sock = TCPSocket.new(@addr, @port)
     req = HTTP_Spew::Request.new(@env, nil, sock)
     assert_equal sock, req.to_io
     assert_nothing_raised { req.close }
@@ -31,13 +31,11 @@ class TestRequest < Test::Unit::TestCase
     sym = req.resume
     assert_kind_of(Symbol, sym)
     if sym == :wait_writable
-      set = Kgio.poll({req => sym}, 1000)
-      assert_equal [ req ], set.keys
+      assert req.to_io.wait_writable(1)
       sym = req.resume
     end
     assert_equal :wait_readable, sym
-    set = Kgio.poll({req => sym}, 1000)
-    assert_equal [ req ], set.keys
+    assert req.to_io.wait_readable(1)
     rv = req.resume
     assert_equal req, rv[2]
   end
@@ -45,7 +43,7 @@ class TestRequest < Test::Unit::TestCase
   def test_request_loop
     req = HTTP_Spew::Request.new(@env, nil, @sockaddr)
     until Array === (rv = req.resume)
-      Kgio.poll(req => rv)
+      req.to_io.__send__(rv) # wait_readable/wait_writable
     end
     assert_kind_of Array, rv
     assert_equal 3, rv.size
diff --git a/test/test_unexpected_response.rb b/test/test_unexpected_response.rb
index bbd63be..2caff05 100644
--- a/test/test_unexpected_response.rb
+++ b/test/test_unexpected_response.rb
@@ -19,7 +19,7 @@ class TestRequest < Test::Unit::TestCase
   end
 
   def test_request_with_existing_socket
-    sock = Kgio::Socket.new(@sockaddr)
+    sock = TCPSocket.new(@addr, @port)
     req = HTTP_Spew::Request.new(@env, nil, sock)
     assert_equal sock, req.to_io
     assert_nothing_raised { req.close }
-- 
EW


^ permalink raw reply related	[flat|nested] 2+ messages in thread

* [PATCH 2/1] make kgio entirely optional
  2017-03-09 20:43 [PATCH] remove most uses of kgio Eric Wong
@ 2017-03-10  0:44 ` Eric Wong
  0 siblings, 0 replies; 2+ messages in thread
From: Eric Wong @ 2017-03-10  0:44 UTC (permalink / raw)
  To: http_spew-public

We may emulate Kgio.poll inefficiently using IO.select; and
perhaps IO.select in Ruby can be modified to transparently use
ppoll() on Linux...
---
 http_spew.gemspec              |  1 -
 lib/http_spew/class_methods.rb | 30 +++++++++++++++++++++++++++++-
 2 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/http_spew.gemspec b/http_spew.gemspec
index d4d38da..a5e4908 100644
--- a/http_spew.gemspec
+++ b/http_spew.gemspec
@@ -16,7 +16,6 @@ Gem::Specification.new do |s|
   s.summary = summary
   s.test_files = Dir["test/test_*.rb"]
   s.add_dependency(%q<kcar>, [ "~> 0.3", ">= 0.3.1"])
-  s.add_dependency(%q<kgio>, "~> 2.6")
   s.add_development_dependency(%q<olddoc>, "~> 1.0")
   s.required_ruby_version = '>= 2.3'
   s.licenses = %w(GPL-2.0+)
diff --git a/lib/http_spew/class_methods.rb b/lib/http_spew/class_methods.rb
index 79b3e47..dda0e19 100644
--- a/lib/http_spew/class_methods.rb
+++ b/lib/http_spew/class_methods.rb
@@ -110,7 +110,7 @@ module HTTP_Spew::ClassMethods
       break if pollset.empty?
 
       busy = pollset.keys
-      rv = with_timeout(t) { Kgio.poll(pollset, (t[0] * 1000).to_i) } or break
+      rv = with_timeout(t) { do_poll(pollset, t[0]) } or break
     end while t[0] > 0.0 && requests = rv.keys.concat(busy).uniq!
 
     ready.concat(failed)
@@ -121,4 +121,32 @@ module HTTP_Spew::ClassMethods
     end
     ready
   end
+
+  begin
+    require 'kgio'
+    def do_poll(pollset, sec) # :nodoc:
+      Kgio.poll(pollset, (sec * 1000).to_i)
+    end
+  rescue LoadError
+    # emulate Kgio.poll with IO.select
+    def do_poll(pollset, sec) # :nodoc:
+      rd = []
+      wr = []
+      pollset.each do |io, events|
+        case events
+        when :wait_readable
+          rd << io
+        when :wait_writable
+          wr << io
+        else
+          raise "BUG: unsupported event #{event.inspect} for #{io.inspect}"
+        end
+      end
+      ready = IO.select(rd, wr, nil, sec) or return
+      pollset.clear
+      ready[0].each { |io| pollset[io] = 1 } # POLLIN
+      ready[1].each { |io| pollset[io] = 4 } # POLLOUT
+      pollset
+    end
+  end
 end
-- 
EW

^ permalink raw reply related	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2017-03-10  0:44 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-03-09 20:43 [PATCH] remove most uses of kgio Eric Wong
2017-03-10  0:44 ` [PATCH 2/1] make kgio entirely optional Eric Wong

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

	https://yhbt.net/http_spew.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox