From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on dcvr.yhbt.net X-Spam-Flag: YES X-Spam-Level: *********** X-Spam-ASN: AS200651 185.100.84.0/23 X-Spam-Status: Yes, score=11.3 required=3.0 tests=BAYES_20, RCVD_IN_BL_SPAMCOP_NET,RCVD_IN_BRBL_LASTEXT,RCVD_IN_MSPIKE_BL, RCVD_IN_MSPIKE_L5,RCVD_IN_PSBL,RCVD_IN_RP_RNBL,RCVD_IN_SBL_CSS,RCVD_IN_XBL, RDNS_NONE,SPF_FAIL,SPF_HELO_FAIL shortcircuit=no autolearn=no autolearn_force=no version=3.4.0 X-Spam-Report: * 3.3 RCVD_IN_SBL_CSS RBL: Received via a relay in Spamhaus SBL-CSS * [185.100.84.108 listed in zen.spamhaus.org] * 0.4 RCVD_IN_XBL RBL: Received via a relay in Spamhaus XBL * 1.4 RCVD_IN_BRBL_LASTEXT RBL: No description available. * [185.100.84.108 listed in bb.barracudacentral.org] * 0.0 RCVD_IN_MSPIKE_L5 RBL: Very bad reputation (-5) * [185.100.84.108 listed in bl.mailspike.net] * 2.7 RCVD_IN_PSBL RBL: Received via a relay in PSBL * [185.100.84.108 listed in psbl.surriel.com] * 1.3 RCVD_IN_BL_SPAMCOP_NET RBL: Received via a relay in bl.spamcop.net * [Blocked - see ] * 1.3 RCVD_IN_RP_RNBL RBL: Relay in RNBL, * https://senderscore.org/blacklistlookup/ * [185.100.84.108 listed in bl.score.senderscore.com] * 0.0 SPF_HELO_FAIL SPF: HELO does not match SPF record (fail) * [SPF failed: Please see http://www.openspf.org/Why?s=helo;id=80x24.org;ip=185.100.84.108;r=dcvr.yhbt.net] * 0.0 SPF_FAIL SPF: sender does not match SPF record (fail) * [SPF failed: Please see http://www.openspf.org/Why?s=mfrom;id=e%4080x24.org;ip=185.100.84.108;r=dcvr.yhbt.net] * -0.0 BAYES_20 BODY: Bayes spam probability is 5 to 20% * [score: 0.0678] * 0.0 RCVD_IN_MSPIKE_BL Mailspike blacklisted * 0.8 RDNS_NONE Delivered to internal network by a host with no rDNS Received: from 80x24.org (unknown [185.100.84.108]) by dcvr.yhbt.net (Postfix) with ESMTP id 454301FA7B for ; Mon, 16 May 2016 01:43:49 +0000 (UTC) From: Eric Wong To: yahns-public@yhbt.net Subject: [PATCH 1/7] proxy_pass: simplify writing request bodies upstream Date: Mon, 16 May 2016 01:43:34 +0000 Message-Id: <20160516014340.8258-2-e@80x24.org> In-Reply-To: <20160516014340.8258-1-e@80x24.org> References: <20160516014340.8258-1-e@80x24.org> List-Id: The cost of extra branches inside a loop is negligible compared to the cost of all the other method calls we make. Favor smaller code instead and inline some (now) single-use methods. Furthermore, this allows us to reuse the request header buffer instead of relying on thread-local storage and potentially having to to swap buffers. --- lib/yahns/proxy_pass.rb | 112 +++++++++++++++++++++--------------------------- 1 file changed, 49 insertions(+), 63 deletions(-) diff --git a/lib/yahns/proxy_pass.rb b/lib/yahns/proxy_pass.rb index 148957b..a2d7d81 100644 --- a/lib/yahns/proxy_pass.rb +++ b/lib/yahns/proxy_pass.rb @@ -22,12 +22,6 @@ def req_start(c, req, input, chunked) Thread.current[:yahns_queue].queue_add(self, Yahns::Queue::QEV_WR) end - # we must reinitialize the thread-local rbuf if it may get beyond the - # current thread - def detach_rbuf! - Thread.current[:yahns_rbuf] = ''.dup - end - def yahns_step # yahns event loop entry point c = @yahns_client case req = @rrstate @@ -42,7 +36,9 @@ def yahns_step # yahns event loop entry point if res = req.headers(@hdr = [], rv) return c.proxy_response_start(res, rv, req, self) else # ugh, big headers or tricked response - buf = detach_rbuf! + # we must reinitialize the thread-local rbuf if it may + # live beyond the current thread + buf = Thread.current[:yahns_rbuf] = ''.dup @resbuf = rv end # continue looping in middle "case @resbuf" loop @@ -83,64 +79,63 @@ def yahns_step # yahns event loop entry point c.proxy_err_response(502, self, e, wbuf) end - # returns :wait_readable if complete, :wait_writable if not - def send_req_body(req) - buf, input, chunked = req - - # get the first buffered chunk or vector + def send_req_body_chunk(buf) case rv = String === buf ? kgio_trywrite(buf) : kgio_trywritev(buf) when String, Array - buf = rv # retry inner loop - when :wait_writable - req[0] = buf - return :wait_writable - when nil - break # onto writing body + buf.replace(rv) # retry loop on partial write + when :wait_writable, nil + # :wait_writable = upstream is reading slowly and making us wait + return rv + else + abort "BUG: #{rv.inspect} from kgio_trywrite*" end while true + end - buf = Thread.current[:yahns_rbuf] + # returns :wait_readable if complete, :wait_writable if not + def send_req_body(req) # @rrstate == [ (str|vec), rack.input, chunked? ] + buf, input, chunked = req - # Note: input (env['rack.input']) is fully-buffered by default so - # we should not be waiting on a slow network resource when reading - # input. However, some weird configs may disable this on LANs + # send the first buffered chunk or vector + rv = send_req_body_chunk(buf) and return rv # :wait_writable + # yay, sent the first chunk, now read the body! + rbuf = buf if chunked - while input.read(0x2000, buf) - vec = [ "#{buf.size.to_s(16)}\r\n", buf, "\r\n".freeze ] - case rv = kgio_trywritev(vec) - when Array - vec = rv # partial write, retry in case loop - when :wait_writable - detach_rbuf! - req[0] = vec - return :wait_writable - when nil - break # continue onto reading next chunk - end while true + if String === buf # initial body + req[0] = buf = [] + else + # try to reuse the biggest non-frozen buffer we just wrote; + rbuf = buf.max_by(&:size) + rbuf = ''.dup if rbuf.frozen? # unlikely... end - close_req_body(input) - - # note: we do not send any trailer, they are folded into the header - # because this relies on full request buffering - send_req_buf("0\r\n\r\n".freeze) - # prepare_wait_readable already called by send_req_buf - else # identity request, easy: - while input.read(0x2000, buf) - case rv = kgio_trywrite(buf) - when String - buf = rv # partial write, retry in case loop - when :wait_writable - detach_rbuf! - req[0] = buf - return :wait_writable - when nil - break # continue onto reading next block - end while true + end + + # Note: input (env['rack.input']) is fully-buffered by default so + # we should not be waiting on a slow network resource when reading + # input. However, some weird configs may disable this on LANs + # and we may wait indefinitely on input.read here... + while input.read(0x2000, rbuf) + if chunked + buf[0] = "#{rbuf.size.to_s(16)}\r\n".freeze + buf[1] = rbuf + buf[2] = "\r\n".freeze end + rv = send_req_body_chunk(buf) and return rv # :wait_writable + end + + rbuf.clear # all done, clear the big buffer - close_req_body(input) - prepare_wait_readable + # we cannot use respond_to?(:close) here since Rack::Lint::InputWrapper + # tries to prevent that (and hijack means all Rack specs go out the door) + case input + when Yahns::TeeInput, IO + input.close end + + # note: we do not send any trailer, they are folded into the header + # because this relies on full request buffering + # prepare_wait_readable is called by send_req_buf + chunked ? send_req_buf("0\r\n\r\n".freeze) : prepare_wait_readable rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ENOTCONN # no more reading off the client socket, just prepare to forward # the rejection response from the upstream (if any) @@ -153,15 +148,6 @@ def prepare_wait_readable :wait_readable # all done sending the request, wait for response end - def close_req_body(input) - # we cannot use respond_to?(:close) here since Rack::Lint::InputWrapper - # tries to prevent that (and hijack means all Rack specs go out the door) - case input - when Yahns::TeeInput, IO - input.close - end - end - # n.b. buf must be a detached string not shared with # Thread.current[:yahns_rbuf] of any thread def send_req_buf(buf)