yahns Ruby server user/dev discussion
 help / color / mirror / code / Atom feed
Search results ordered by [date|relevance]  view[summary|nested|Atom feed]
thread overview below | download mbox.gz: |
* [PATCH 2/2] wbuf_lite: fix write retries for OpenSSL sockets
  @ 2016-06-06  5:14  4% ` Eric Wong
  0 siblings, 0 replies; 7+ results
From: Eric Wong @ 2016-06-06  5:14 UTC (permalink / raw)
  To: yahns-public

OpenSSL can't handle write retries if we append to an existing
string.  Thus we must preserve the same string object upon
retrying.

Do that by utilizing the underlying Wbuf class which could
already handles it transparently using trysendfile.
However, we still avoiding the subtlety of wbuf_close_common
reliance we previously used.

ref: commit 551e670281bea77e727a732ba94275265ccae5f6
("fix output buffering with SSL_write")
---
 lib/yahns/wbuf_lite.rb               | 46 ++++++++++++++++--------------------
 test/test_proxy_pass_no_buffering.rb |  7 +++++-
 2 files changed, 26 insertions(+), 27 deletions(-)

diff --git a/lib/yahns/wbuf_lite.rb b/lib/yahns/wbuf_lite.rb
index 5f25b2e..afee1e9 100644
--- a/lib/yahns/wbuf_lite.rb
+++ b/lib/yahns/wbuf_lite.rb
@@ -2,49 +2,43 @@
 # Copyright (C) 2016 all contributors <yahns-public@yhbt.net>
 # License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
 # frozen_string_literal: true
-require_relative 'wbuf_common'
+require_relative 'wbuf'
 
 # This is only used for "proxy_buffering: false"
-class Yahns::WbufLite # :nodoc:
-  include Yahns::WbufCommon
+class Yahns::WbufLite < Yahns::Wbuf # :nodoc:
   attr_reader :busy
 
   def initialize(req_res)
+    super(nil, :ignore)
     @req_res = req_res
-    @busy = false
-    @buf = nil
   end
 
-  # called only by proxy_write in proxy_http_response
   def wbuf_write(client, buf)
-    buf = buf.join if Array === buf
-    buf = @buf << buf if @buf # unlikely
-    do_write(client, buf) # try our best to avoid string copying/appending
+    super
+  rescue
+    @req_res = @req_res.close if @req_res
+    raise
   end
 
-  def do_write(client, buf)
-    case rv = client.kgio_trywrite(buf)
-    when String
-      buf = rv # continue looping
-    when :wait_writable, :wait_readable
-      @buf = buf
-      return @busy = rv
-    when nil
-      @buf = nil
-      return @busy = false
-    end while true
+  def wbuf_flush(client)
+    super
+  rescue
+    @req_res = @req_res.close if @req_res
+    raise
   end
 
   # called by Yahns::HttpClient#step_write
-  def wbuf_flush(client)
-    sym = do_write(client, @buf) and return sym # :wait_writable/:wait_readable
+  def wbuf_close(client)
+    wbuf_abort
 
-    # resume reading
-    client.hijack_cleanup
-    Thread.current[:yahns_queue].queue_mod(@req_res, Yahns::Queue::QEV_RD)
+    # resume reading when @blocked is empty
+    if @req_res
+      client.hijack_cleanup
+      Thread.current[:yahns_queue].queue_mod(@req_res, Yahns::Queue::QEV_RD)
+    end
     :ignore
   rescue
-    @req_res.close
+    @req_res = @req_res.close if @req_res
     raise
   end
 end
diff --git a/test/test_proxy_pass_no_buffering.rb b/test/test_proxy_pass_no_buffering.rb
index 48b8241..b2c8b48 100644
--- a/test/test_proxy_pass_no_buffering.rb
+++ b/test/test_proxy_pass_no_buffering.rb
@@ -79,6 +79,7 @@ def test_proxy_pass_no_buffering
     req = "GET /giant-body HTTP/1.1\r\nHost: example.com\r\n" \
           "Connection: close\r\n\r\n"
     s.write(req)
+    bufs = []
     sleep 1
     10.times do
       sleep 0.1
@@ -91,10 +92,14 @@ def test_proxy_pass_no_buffering
         [ deleted1, deleted2 ].each do |ary|
           ary.delete_if { |x| x =~ /\.(?:err|out) \(deleted\)/ }
         end
-        assert_equal 0, deleted1.size, "pid1=#{deleted1.inspect}"
+        assert_equal 1, deleted1.size, "pid1=#{deleted1.inspect}"
         assert_equal 0, deleted2.size, "pid2=#{deleted2.inspect}"
+        bufs.push(deleted1[0])
       end
     end
+    before = bufs.size
+    bufs.uniq!
+    assert bufs.size < before, 'unlinked buffer should not grow'
     buf = ''.dup
     slow = Digest::MD5.new
     ft = Thread.new do

^ permalink raw reply related	[relevance 4%]

* [PATCH] ssl: disable copying for writes
@ 2016-03-01  1:30  4% Eric Wong
  0 siblings, 0 replies; 7+ results
From: Eric Wong @ 2016-03-01  1:30 UTC (permalink / raw)
  To: yahns-public

This is a partial revert of 551e670281bea77e727a732ba94275265ccae5f6
"fix output buffering with SSL_write".

With the patch to enable SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
in <https://bugs.ruby-lang.org/issues/12126>, we can avoid
the hacky workaround we do.

We still implement our own trysendfile to avoid overhead
of string allocation kgio_trywrite and we still fill in
the missing kgio_trywritev addition.
---
  Consider this patch on hold.  Of course we cannot expect
  users to manually patch or run ruby-trunk in production.

  Pushed to the "ruby-accept_moving_write_buffer" branch
  in git://yhbt.net/yahns  I will rebase this branch as
  necessary.

 lib/yahns/openssl_client.rb | 23 ++++++-----------------
 1 file changed, 6 insertions(+), 17 deletions(-)

diff --git a/lib/yahns/openssl_client.rb b/lib/yahns/openssl_client.rb
index cd7d210..30ac0e0 100644
--- a/lib/yahns/openssl_client.rb
+++ b/lib/yahns/openssl_client.rb
@@ -36,18 +36,15 @@ def sync
   def yahns_init_ssl(ssl_ctx)
     @need_accept = true
     @ssl = OpenSSL::SSL::SSLSocket.new(self, ssl_ctx)
-    @ssl_blocked = nil
   end
 
   def kgio_trywrite(buf)
-    buf = @ssl_blocked = buf.dup
     case rv = @ssl.write_nonblock(buf, exception: false)
     when :wait_readable, :wait_writable
-      return rv # do not clear ssl_blocked
+      return rv
     when Integer
       rv = buf.bytesize == rv ? nil : buf.byteslice(rv, buf.bytesize - rv)
     end
-    @ssl_blocked = nil
     rv
   end
 
@@ -75,22 +72,14 @@ def kgio_tryread(len, buf)
   def trysendfile(io, offset, count)
     return 0 if count == 0
 
-    unless buf = @ssl_blocked
-      count = 0x4000 if count > 0x4000
-      buf = Thread.current[:yahns_sfbuf] ||= ''.dup
-      io.pos = offset
-      buf = io.read(count, buf) or return # nil for EOF
-      buf = @ssl_blocked = buf.dup
-    end
+    count = 0x4000 if count > 0x4000
+    buf = Thread.current[:yahns_sfbuf] ||= ''.dup
+    io.pos = offset
+    buf = io.read(count, buf) or return # nil for EOF
 
     # call write_nonblock directly since kgio_trywrite allocates
     # an unnecessary string
-    case rv = @ssl.write_nonblock(buf, exception: false)
-    when :wait_readable, :wait_writable
-      return rv # do not clear ssl_blocked
-    end
-    @ssl_blocked = nil
-    rv
+    @ssl.write_nonblock(buf, exception: false)
   end
 
   def close
-- 
EW

^ permalink raw reply related	[relevance 4%]

* [ANN] yahns 1.12.1 -_- sleepy app server for Ruby
  @ 2016-02-22  0:43  5% ` Eric Wong
  0 siblings, 0 replies; 7+ results
From: Eric Wong @ 2016-02-22  0:43 UTC (permalink / raw)
  To: ruby-talk, yahns-public

A Free Software, multi-threaded, non-blocking network
application server designed for low _idle_ power consumption.
It is primarily optimized for applications with occasional users
which see little or no traffic.  yahns currently hosts Rack/HTTP
applications, but may eventually support other application
types.  Unlike some existing servers, yahns is extremely
sensitive to fatal bugs in the applications it hosts.

Changes:

    yahns 1.12.1 - more TLS fixes

    Most notably release fixes TLS output buffering for large
    responses to slow clients.  For Rack HTTPS users,
    env['SERVER_PORT'] also defaults to 443 properly unless the
    Host: request header specifies differently.

    Also, the extras/autoindex change is to make our own directory
    listing look nicer as we use Let's Encrypt and don't want to
    waste space listing ".well-known/" directory contents on:

	https://yahns.yhbt.net/

    Yes, we really do care how our homepage looks!

    6 changes since v1.12.0:
          extras/autoindex: support hiding dotfiles
          fix output buffering with SSL_write
          https: ensure SERVER_PORT defaults to 443
          test_ssl: check SERVER_PORT when parsed from Host: header
          doc: mention kqueue/kevent alongside epoll
          doc: more minor updates

Please note the disclaimer:

  yahns is extremely sensitive to fatal bugs in the apps it hosts.  There
  is no (and never will be) any built-in "watchdog"-type feature to kill
  stuck processes/threads.  Each yahns process may be handling thousands
  of clients; unexpectedly killing the process will abort _all_ of those
  connections.  Lives may be lost!

  yahns hackers are not responsible for your application/library bugs.
  Use an application server which is tolerant of buggy applications
  if you cannot be bothered to fix all your fatal bugs.

* git clone git://yhbt.net/yahns
* http://yahns.yhbt.net/README
* http://yahns.yhbt.net/NEWS.atom.xml
* we only accept plain-text email yahns-public@yhbt.net
* and archive all the mail we receive: http://yhbt.net/yahns-public/
* nntp://news.public-inbox.org/inbox.comp.lang.ruby.yahns

^ permalink raw reply	[relevance 5%]

* Re: [PATCH] fix output buffering with SSL_write
  2016-02-20  2:54  4%   ` [PATCH] " Eric Wong
@ 2016-02-20 21:33  7%     ` Eric Wong
  0 siblings, 0 replies; 7+ results
From: Eric Wong @ 2016-02-20 21:33 UTC (permalink / raw)
  To: yahns-public

Eric Wong <e@80x24.org> wrote:
>  lib/yahns/openssl_client.rb | 40 +++++++++++++++++++++++++++++++++++-----
>  1 file changed, 35 insertions(+), 5 deletions(-)

Btw, reproducing this as a standalone test case over loopback
seems difficult, even with small socket buffers.
It could also be cipher/algorithm-dependent...

I never hit problems with v1.12.0 when git-cloning
https://yhbt.net/yahns-public locally on the server, only from a
remote connection.  Same thing with largish static files.

So I'm tempted to release it as-is without an additional test case.

^ permalink raw reply	[relevance 7%]

* [PATCH] fix output buffering with SSL_write
  2016-02-16 11:14  5% ` [WIP OPTION #2] " Eric Wong
@ 2016-02-20  2:54  4%   ` Eric Wong
  2016-02-20 21:33  7%     ` Eric Wong
  0 siblings, 1 reply; 7+ results
From: Eric Wong @ 2016-02-20  2:54 UTC (permalink / raw)
  To: yahns-public

The underlying SSL_write called by the OpenSSL socket when
we use write_nonblock must get the same arguments after a
call returns SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.

Ensure that by always passing a copy of the user-supplied
buffer to OpenSSL::SSL::SSLSocket#write_nonblock and retaining
our copy of the string internally as @ssl_blocked if we hit
EAGAIN on the socket.

String#dup is inexpensive in modern Ruby, as copying a
non-embedded string is implemented using copy-on-write.

We also prefer to use write_nonblock directly instead of
using our kgio-dependent sendfile emulation layer to avoid
allocating a new string on partial writes.

ref: https://bugs.ruby-lang.org/issues/12085
http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/73882
http://mid.gmane.org/redmine.issue-12085.20160219020243.4b790a77f1cdd593@ruby-lang.org
---
  Eric Wong <e@80x24.org> wrote:
  > --- a/lib/yahns/openssl_client.rb
  > +++ b/lib/yahns/openssl_client.rb

  <snip>

  >    def kgio_trywrite(buf)
  > +    ssl_blocked = @ssl_blocked
  > +    if ! ssl_blocked.empty? && ssl_blocked.object_id != buf.object_id
  > +      warn "bk: #{ssl_blocked.inspect}\n!= #{buf.inspect}"
  > +    end
  > +    warn "buf: #{buf.inspect}" if buf.bytesize <= 23
  > +    #warn "b: #{buf.object_id} #{buf.object_id == ssl_blocked.object_id}"
  > +    buf = ssl_blocked.replace(buf) # buf and ssl_blocked may be the same obj
  > +    buf.force_encoding Encoding::BINARY
  >      rv = @ssl.write_nonblock(buf, exception: false)
  > -    Integer === rv and
  > +    case rv
  > +    when :wait_readable, :wait_writable
  > +      #warn "#{fileno} blocking #{buf.bytesize} #{buf.object_id}"
  > +      return rv # do not clear ssl_blocked
  > +    when Integer
  >        rv = buf.bytesize == rv ? nil : buf.byteslice(rv, buf.bytesize)
  > +    end
  > +    ssl_blocked.clear

  The above call to String#clear interacts badly with the following:

  > --- a/lib/yahns/sendfile_compat.rb
  > +++ b/lib/yahns/sendfile_compat.rb
  > @@ -6,19 +6,29 @@
  >  module Yahns::SendfileCompat # :nodoc:
  >    def trysendfile(io, offset, count)

  <snip>

  >      case rv = kgio_trywrite(str)
  >      when String # partial write, keep trying
  > -      n += (str.size - rv.size)
  > +      n += (str.bytesize - rv.size)
  >        str = rv
  >      when :wait_writable, :wait_readable
  > +      if respond_to?(:ssl_blocked)
  > +        #warn "empty: #{@ssl_blocked.empty?}"
  > +      end
  >        return n > 0 ? n : rv
  >      when nil
  > -      return n + str.size # yay!
  > +      return n + str.bytesize # yay!
  >      end while true
  >    end
  >  end

  ... because str.size becomes zero when String#clear got called
  Ooops :x

 lib/yahns/openssl_client.rb | 40 +++++++++++++++++++++++++++++++++++-----
 1 file changed, 35 insertions(+), 5 deletions(-)

diff --git a/lib/yahns/openssl_client.rb b/lib/yahns/openssl_client.rb
index bf64255..cd7d210 100644
--- a/lib/yahns/openssl_client.rb
+++ b/lib/yahns/openssl_client.rb
@@ -1,3 +1,4 @@
+# -*- encoding: binary -*-
 # Copyright (C) 2013-2016 all contributors <yahns-public@yhbt.net>
 # License: GPL-3.0+ (https://www.gnu.org/licenses/gpl-3.0.txt)
 # frozen_string_literal: true
@@ -7,8 +8,6 @@
 # this is to be included into a Kgio::Socket-derived class
 # this requires Ruby 2.1 and later for "exception: false"
 module Yahns::OpenSSLClient # :nodoc:
-  include Yahns::SendfileCompat
-
   def self.included(cls)
     # Forward these methods to OpenSSL::SSL::SSLSocket so hijackers
     # can rely on stdlib methods instead of ugly kgio stuff that
@@ -37,15 +36,25 @@ def sync
   def yahns_init_ssl(ssl_ctx)
     @need_accept = true
     @ssl = OpenSSL::SSL::SSLSocket.new(self, ssl_ctx)
+    @ssl_blocked = nil
   end
 
   def kgio_trywrite(buf)
-    rv = @ssl.write_nonblock(buf, exception: false)
-    Integer === rv and
-      rv = buf.bytesize == rv ? nil : buf.byteslice(rv, buf.bytesize)
+    buf = @ssl_blocked = buf.dup
+    case rv = @ssl.write_nonblock(buf, exception: false)
+    when :wait_readable, :wait_writable
+      return rv # do not clear ssl_blocked
+    when Integer
+      rv = buf.bytesize == rv ? nil : buf.byteslice(rv, buf.bytesize - rv)
+    end
+    @ssl_blocked = nil
     rv
   end
 
+  def kgio_trywritev(buf)
+    kgio_trywrite(buf.join)
+  end
+
   def kgio_syssend(buf, flags)
     kgio_trywrite(buf)
   end
@@ -63,6 +72,27 @@ def kgio_tryread(len, buf)
     @ssl.read_nonblock(len, buf, exception: false)
   end
 
+  def trysendfile(io, offset, count)
+    return 0 if count == 0
+
+    unless buf = @ssl_blocked
+      count = 0x4000 if count > 0x4000
+      buf = Thread.current[:yahns_sfbuf] ||= ''.dup
+      io.pos = offset
+      buf = io.read(count, buf) or return # nil for EOF
+      buf = @ssl_blocked = buf.dup
+    end
+
+    # call write_nonblock directly since kgio_trywrite allocates
+    # an unnecessary string
+    case rv = @ssl.write_nonblock(buf, exception: false)
+    when :wait_readable, :wait_writable
+      return rv # do not clear ssl_blocked
+    end
+    @ssl_blocked = nil
+    rv
+  end
+
   def close
     @ssl.close # flushes SSLSocket
     super # IO#close
-- 
EW

^ permalink raw reply related	[relevance 4%]

* [WIP OPTION #2] fix output buffering with SSL_write
  2016-02-16 11:11  5% [WIP OPTION #1] fix output buffering with SSL_write Eric Wong
@ 2016-02-16 11:14  5% ` Eric Wong
  2016-02-20  2:54  4%   ` [PATCH] " Eric Wong
  0 siblings, 1 reply; 7+ results
From: Eric Wong @ 2016-02-16 11:14 UTC (permalink / raw)
  To: yahns-public

The underlying SSL_write called by the OpenSSL socket when
we use write_nonblock must get the same arguments after a
call returns SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.

Theoretically this works better than option #1 w.r.t
embedded strings.  But it falls down badly when I run:

  git clone --mirror https://yhbt.net/yahns-public

To clone the mailing list archives.  Plain HTTP works fine.
So I'm running Option #1 on https://yhbt.net/, for now...

Sleepy and tired now :<
---
 lib/yahns/openssl_client.rb      | 21 ++++++++++++++++++++-
 lib/yahns/proxy_http_response.rb |  1 +
 lib/yahns/sendfile_compat.rb     | 22 ++++++++++++++++------
 lib/yahns/wbuf.rb                |  3 +++
 4 files changed, 40 insertions(+), 7 deletions(-)

diff --git a/lib/yahns/openssl_client.rb b/lib/yahns/openssl_client.rb
index bf64255..4587483 100644
--- a/lib/yahns/openssl_client.rb
+++ b/lib/yahns/openssl_client.rb
@@ -37,15 +37,34 @@ def sync
   def yahns_init_ssl(ssl_ctx)
     @need_accept = true
     @ssl = OpenSSL::SSL::SSLSocket.new(self, ssl_ctx)
+    @ssl_blocked = ''.dup
   end
 
   def kgio_trywrite(buf)
+    ssl_blocked = @ssl_blocked
+    if ! ssl_blocked.empty? && ssl_blocked.object_id != buf.object_id
+      warn "bk: #{ssl_blocked.inspect}\n!= #{buf.inspect}"
+    end
+    warn "buf: #{buf.inspect}" if buf.bytesize <= 23
+    #warn "b: #{buf.object_id} #{buf.object_id == ssl_blocked.object_id}"
+    buf = ssl_blocked.replace(buf) # buf and ssl_blocked may be the same obj
+    buf.force_encoding Encoding::BINARY
     rv = @ssl.write_nonblock(buf, exception: false)
-    Integer === rv and
+    case rv
+    when :wait_readable, :wait_writable
+      #warn "#{fileno} blocking #{buf.bytesize} #{buf.object_id}"
+      return rv # do not clear ssl_blocked
+    when Integer
       rv = buf.bytesize == rv ? nil : buf.byteslice(rv, buf.bytesize)
+    end
+    ssl_blocked.clear
     rv
   end
 
+  def kgio_trywritev(buf)
+    abort 'BUG: we cannot use writev with OpenSSL'
+  end
+
   def kgio_syssend(buf, flags)
     kgio_trywrite(buf)
   end
diff --git a/lib/yahns/proxy_http_response.rb b/lib/yahns/proxy_http_response.rb
index 0a7e722..287d039 100644
--- a/lib/yahns/proxy_http_response.rb
+++ b/lib/yahns/proxy_http_response.rb
@@ -11,6 +11,7 @@ module Yahns::HttpResponse # :nodoc:
   # it may return a newly-created wbuf or nil
   def proxy_write(wbuf, buf, alive)
     unless wbuf
+      buf = buf.join if Array === buf && respond_to?(:yahns_init_ssl)
       # no write buffer, try to write directly to the client socket
       case rv = String === buf ? kgio_trywrite(buf) : kgio_trywritev(buf)
       when nil then return # done writing buf, likely
diff --git a/lib/yahns/sendfile_compat.rb b/lib/yahns/sendfile_compat.rb
index 8bd4622..e382f18 100644
--- a/lib/yahns/sendfile_compat.rb
+++ b/lib/yahns/sendfile_compat.rb
@@ -6,19 +6,29 @@
 module Yahns::SendfileCompat # :nodoc:
   def trysendfile(io, offset, count)
     return 0 if count == 0
-    count = 0x4000 if count > 0x4000
-    buf = Thread.current[:yahns_sfbuf] ||= ''.dup
-    io.pos = offset
-    str = io.read(count, buf) or return # nil for EOF
+
+    if respond_to?(:yahns_init_ssl) && !@ssl_blocked.empty?
+      str = @ssl_blocked
+      #warn "#{fileno} unblocking #{str.bytesize} #{str.object_id}"
+    else
+      count = 0x4000 if count > 0x4000
+      buf = Thread.current[:yahns_sfbuf] ||= ''.dup
+      io.pos = offset
+      str = io.read(count, buf) or return # nil for EOF
+    end
+
     n = 0
     case rv = kgio_trywrite(str)
     when String # partial write, keep trying
-      n += (str.size - rv.size)
+      n += (str.bytesize - rv.size)
       str = rv
     when :wait_writable, :wait_readable
+      if respond_to?(:ssl_blocked)
+        #warn "empty: #{@ssl_blocked.empty?}"
+      end
       return n > 0 ? n : rv
     when nil
-      return n + str.size # yay!
+      return n + str.bytesize # yay!
     end while true
   end
 end
diff --git a/lib/yahns/wbuf.rb b/lib/yahns/wbuf.rb
index 1b4ce6e..ff67e0c 100644
--- a/lib/yahns/wbuf.rb
+++ b/lib/yahns/wbuf.rb
@@ -48,6 +48,9 @@ def wbuf_writev(buf)
   end
 
   def wbuf_write(c, buf)
+    ssl = c.respond_to?(:yahns_init_ssl)
+    buf = buf.join if ssl && Array === buf
+
     # try to bypass the VFS layer and write directly to the socket
     # if we're all caught up
     case rv = String === buf ? c.kgio_trywrite(buf) : c.kgio_trywritev(buf)
-- 
EW


^ permalink raw reply related	[relevance 5%]

* [WIP OPTION #1] fix output buffering with SSL_write
@ 2016-02-16 11:11  5% Eric Wong
  2016-02-16 11:14  5% ` [WIP OPTION #2] " Eric Wong
  0 siblings, 1 reply; 7+ results
From: Eric Wong @ 2016-02-16 11:11 UTC (permalink / raw)
  To: yahns-public

The underlying SSL_write called by the OpenSSL socket when
we use write_nonblock must get the same arguments after a
call returns SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE.

FIXME: this probably breaks badly for embedded strings
(but it's tricky to test...)
---
 lib/yahns/openssl_client.rb      |  5 +++++
 lib/yahns/proxy_http_response.rb |  1 +
 lib/yahns/sendfile_compat.rb     | 23 +++++++++++++++++------
 lib/yahns/wbuf.rb                | 14 +++++++++++++-
 4 files changed, 36 insertions(+), 7 deletions(-)

diff --git a/lib/yahns/openssl_client.rb b/lib/yahns/openssl_client.rb
index bf64255..c5b5c8a 100644
--- a/lib/yahns/openssl_client.rb
+++ b/lib/yahns/openssl_client.rb
@@ -37,6 +37,7 @@ def sync
   def yahns_init_ssl(ssl_ctx)
     @need_accept = true
     @ssl = OpenSSL::SSL::SSLSocket.new(self, ssl_ctx)
+    @ssl_blocked = false
   end
 
   def kgio_trywrite(buf)
@@ -46,6 +47,10 @@ def kgio_trywrite(buf)
     rv
   end
 
+  def kgio_trywritev(buf)
+    abort 'BUG: we cannot use writev with OpenSSL'
+  end
+
   def kgio_syssend(buf, flags)
     kgio_trywrite(buf)
   end
diff --git a/lib/yahns/proxy_http_response.rb b/lib/yahns/proxy_http_response.rb
index 0a7e722..287d039 100644
--- a/lib/yahns/proxy_http_response.rb
+++ b/lib/yahns/proxy_http_response.rb
@@ -11,6 +11,7 @@ module Yahns::HttpResponse # :nodoc:
   # it may return a newly-created wbuf or nil
   def proxy_write(wbuf, buf, alive)
     unless wbuf
+      buf = buf.join if Array === buf && respond_to?(:yahns_init_ssl)
       # no write buffer, try to write directly to the client socket
       case rv = String === buf ? kgio_trywrite(buf) : kgio_trywritev(buf)
       when nil then return # done writing buf, likely
diff --git a/lib/yahns/sendfile_compat.rb b/lib/yahns/sendfile_compat.rb
index 8bd4622..4842cda 100644
--- a/lib/yahns/sendfile_compat.rb
+++ b/lib/yahns/sendfile_compat.rb
@@ -4,21 +4,32 @@
 # frozen_string_literal: true
 
 module Yahns::SendfileCompat # :nodoc:
+  attr_accessor :ssl_blocked
+
   def trysendfile(io, offset, count)
     return 0 if count == 0
-    count = 0x4000 if count > 0x4000
-    buf = Thread.current[:yahns_sfbuf] ||= ''.dup
-    io.pos = offset
-    str = io.read(count, buf) or return # nil for EOF
+
+    if respond_to?(:yahns_init_ssl) && @ssl_blocked
+      str = @ssl_blocked
+      @ssl_blocked = false
+    else
+      count = 0x4000 if count > 0x4000
+      buf = Thread.current[:yahns_sfbuf] ||= ''.dup
+      io.pos = offset
+      str = io.read(count, buf) or return # nil for EOF
+    end
+
     n = 0
     case rv = kgio_trywrite(str)
     when String # partial write, keep trying
-      n += (str.size - rv.size)
+      n += (str.bytesize - rv.size)
       str = rv
     when :wait_writable, :wait_readable
+      @ssl_blocked = str
+      Thread.current[:yahns_sfbuf] = ''.dup
       return n > 0 ? n : rv
     when nil
-      return n + str.size # yay!
+      return n + str.bytesize # yay!
     end while true
   end
 end
diff --git a/lib/yahns/wbuf.rb b/lib/yahns/wbuf.rb
index 1b4ce6e..3dddb95 100644
--- a/lib/yahns/wbuf.rb
+++ b/lib/yahns/wbuf.rb
@@ -48,6 +48,9 @@ def wbuf_writev(buf)
   end
 
   def wbuf_write(c, buf)
+    ssl = c.respond_to?(:yahns_init_ssl)
+    buf = buf.join if ssl && Array === buf
+
     # try to bypass the VFS layer and write directly to the socket
     # if we're all caught up
     case rv = String === buf ? c.kgio_trywrite(buf) : c.kgio_trywritev(buf)
@@ -60,7 +63,16 @@ def wbuf_write(c, buf)
     end until @busy
 
     @tmpio ||= Yahns::TmpIO.new(@tmpdir)
-    @sf_count += String === buf ? @tmpio.write(buf) : wbuf_writev(buf)
+    if @sf_count == 0 && c.respond_to?(:yahns_init_ssl)
+      # We need to maintain a consistent RSTRING_PTR and MRI strings are CoW
+      # XXX: this breaks if we have embedded strings!
+      c.ssl_blocked = buf.dup
+      # we need to maintain placeholder in tmpio so
+      # sf_offset remains consistent for wbuf_flush
+      @sf_count = @tmpio.write(buf)
+    else
+      @sf_count += String === buf ? @tmpio.write(buf) : wbuf_writev(buf)
+    end
 
     # we spent some time copying to the FS, try to write to
     # the socket again in case some space opened up...
-- 
EW


^ permalink raw reply related	[relevance 5%]

Results 1-7 of 7 | reverse | options above
-- pct% links below jump to the message on this page, permalinks otherwise --
2016-02-14 22:37     [ANN] yahns 1.12.0 -_- sleepy app server for Ruby Eric Wong
2016-02-22  0:43  5% ` [ANN] yahns 1.12.1 " Eric Wong
2016-02-16 11:11  5% [WIP OPTION #1] fix output buffering with SSL_write Eric Wong
2016-02-16 11:14  5% ` [WIP OPTION #2] " Eric Wong
2016-02-20  2:54  4%   ` [PATCH] " Eric Wong
2016-02-20 21:33  7%     ` Eric Wong
2016-03-01  1:30  4% [PATCH] ssl: disable copying for writes Eric Wong
2016-06-06  5:14     [YUCK 0/2] wbuf_lite: fix write retries for OpenSSL Eric Wong
2016-06-06  5:14  4% ` [PATCH 2/2] wbuf_lite: fix write retries for OpenSSL sockets Eric Wong

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

	https://yhbt.net/yahns.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).