Date | Commit message (Collapse) |
|
We don't want to wait on GC to reap sockets on errors,
generational GC in Ruby is less aggressive about reaping
long-lived objects such as long-lived HTTP connections.
|
|
For slow clients, we want to be able to drop the connection
to the upstream as soon as we are done buffering and not waste
resources by leaving it in an :ignore state. We also need to
remember the client for the fdmap to prevent shutdowns.
Ugh, this is really hard to test locally.
|
|
We already check for the truthiness of "alive" in the "if"
statement, so re-setting is pointless.
|
|
Without this, non-terminated backends were not properly
supported if they gave tiny responses or responded faster
than we could stream the response to the client.
This is necessary to support fast responses from some non-Rack
HTTP/1.0-only backend servers which rely on connection
termination to terminate responses.
Tested manually with a Perl PSGI application running under
"plackup". Unlike Rack, the PSGI spec does not specify whether
the PSGI application or PSGI server should handle response
termination: git clone https://github.com/plack/psgi-specs.git
Follow-up-to: 8c9f33a5396d2 ("workaround non-terminated backends")
|
|
If a static file response gets truncated while writing, we
set wbuf_persist to false in wbuf_flush of lib/yahns/wbuf_common.rb
Thus, we must check wbuf_persist attribute as late as possible
before yielding control back to the caller in proxy_response_finish
|
|
Without this, we could only support persistent connections if
the backend gives a valid Content-Length or set
"Transfer-Encoding: chunked" in the response header.
Being good netizens, we want to use persistent connections as
much as possible if a remote client supports it; so perform
chunking ourselves when our remote clients are HTTP/1.1 and
able to decode chunked responses.
This is necessary to support some non-Rack HTTP/1.0-only
backend servers which rely on connection termination to
terminate responses.
Tested manually with a Perl PSGI application running under
"plackup". Unlike Rack, the PSGI spec does not specify whether
the PSGI application or PSGI server should handle response
termination: git clone https://github.com/plack/psgi-specs.git
|
|
Using the 'update-copyright' script from gnulib[1]:
git ls-files | UPDATE_COPYRIGHT_HOLDER='all contributors' \
UPDATE_COPYRIGHT_USE_INTERVALS=2 \
xargs /path/to/gnulib/build-aux/update-copyright
We're also switching to 'GPL-3.0+' as recommended by SPDX
to be consistent with our gemspec and other metadata
(as opposed to the longer but equivalent "GPLv3 or later").
[1] git://git.savannah.gnu.org/gnulib.git
|
|
There are likely yet-to-be-discovered bugs in here.
Also, keeping explicit #freeze calls for 2.2 users, since most
users have not migrated to 2.3, yet.
|
|
This can reduce the amount of garbage we have by a small
amount. Once Ruby 2.3 comes out, we can rely on the
"frozen_string_literal: true" directive
|
|
Ruby (MRI) 2.1 optimizes allocations away on String#freeze with
literal strings.
Furthermore, Ruby 2.2 optimizes away literal string allocations
when they are used as arguments to Hash#[] and Hash#[]=
Thus we can avoid expensive constant lookups and cache overhead
by taking advantage of advancements in Ruby.
Since Ruby 2.2 has been out for 7 months, now; it ought to be safe
to introduce minor performance regressions for folks using older
Rubies (1.9.3+ remains supported) to benefit folks on the latest
Ruby.
|
|
Rack::Utils::HTTP_STATUS_CODES may be altered by the underlying
application, allow changes to that to be reflected in our responses
and do not rely on the Unicorn::HttpResponse::CODES hash which
will probably go away soon.
|
|
We shouldn't blindly pass the "Server" tag through, since we may
be proxying Apache instances and we don't want to misadvertise,
either.
IMHO, it is best to say nothing at all to save bandwidth and
reduce the potential for attackers in case a vulnerability is
discovered in yahns.
|
|
When calling proxy_busy_mod_blocked to re-enable a descriptor via
epoll, the ensure block is dangerous because the "ensure" clause
modifies the object after the ReqRes is injected into epoll.
This is extremely dangerous as we give up exclusive access to
the object once we call epoll_ctl.
This simplifies the code a bit while we're at it.
|
|
Reactivating a client socket after the proxied response is
complete requires the object remain visible to the Ruby GC while
no thread is accessing it. So we must place the object back
into the fdmap to prevent the GC from eating it (and having
epoll return an invalid pointer).
|
|
The entire idea of a one-shot-based design is all the mutual
exclusion is handled by the event dispatch mechanism (epoll or
kqueue) without burdening the user with extra locking. However, the
way the hijack works means we check the Rack env for the
'rack.hijack_io' key which is shared across requests and may
be cleared.
Ideally, this would not be a problem if the Rack dispatch allowed
returning a special value (e.g. ":ignore") instead of the normal
status-headers-body array, much like what the non-standard
"async.callback" API Thin started.
We could also avoid this problem by disallowing our "unhijack-ing"
of the socket but at a significant cost of crippling code
reusability, including that of existing middleware.
Thus, we allocate a new, empty request object here to avoid a TOCTTOU
in the following timeline:
original thread: | another thread
HttpClient#yahns_step |
r = k.app.call(env = @hs.env) # socket hijacked into epoll queue
<thread is scheduled away> | epoll_wait readiness
| ReqRes#yahns_step
| proxy dispatch ...
| proxy_busy_mod_done
************************** DANGER BELOW ********************************
| HttpClient#yahns_step
| # clears env
# sees empty env: |
return :ignore if env.include?('rack.hijack_io') |
In other words, we cannot ever touch the original env seen by the
original thread since it must see the 'rack.hijack_io' value because
both are operating in the same Yahns::HttpClient object. This will
happen regardless of GVL existence.
Avoiding errors like this is absolutely critical to every one-shot
based design.
|
|
Not all backends are capable of generating chunked responses
(especially not to HTTP/1.0 clients) nor can they generate
the Content-Length (especially when gzipping), so they'll
close the socket to signal EOF instead.
|
|
We cannot pass trailers from upstreams to HTTP/1.0 clients without
fully-buffering the response body AND trailers before forwarding the
response header.
Of course, one of the reasons yahns exists is to support lazy
buffering, so fully-buffering up front is wasteful and hurts
latency. So instead, degrade to 1.0 requests to upstreams for
HTTP/1.0 clients, this should prevent upstreams from sending
trailers in the first place.
HTTP/1.0 clients on Rails apps may suffer, but there probably
are not too many HTTP/1.0 clients out there.
|
|
Rack apps may (through a round-about way) send HTTP trailers
to HTTP/1.1 clients, and we need a way to forward those responses
through without losing the trailers.
|
|
We need to ensure more uncommon cases such as gigantic upstream
headers and truncated upstream responses are handled properly
and predictably.
|
|
This allows our reverse proxy to avoid having an innefficient 1:1
relationship between threads and upstream connections, reducing
memory usage when there are many upstream connections (possibly to
multiple backend machines).
|