Date | Commit message (Collapse) |
|
Most notably, the Rack response body is now closed during rack.hijack.
Middlewares such as Rack::Lock (used by Rails) break badly unless
the response body is closed on hijack, so we will close it to follow
the lead of other popular Rack servers.
While it's unclear if there's anybody using rack.hijack besides
yahns/proxy_pass we'll try to emulate the behavior of other servers
as much as possible.
ref: https://github.com/ngauthier/tubesock/issues/10
We'll also support SIGWINCH if not daemonized
This has no effect for the (default) single process case with
no master/worker relationship as that does not support SIGWINCH.
Some process managers such as foreman and daemontools rely on
yahnsnot daemonizing, but we still want to be able to process
SIGWINCH in that case.
stdout and stderr may be redirected to a pipe (for cronolog or
similar process), so those are less likely to be attached to a TTY
than stdin. This also allows users to process SIGWINCH when running
inside a regular terminal if they redirect stdin to /dev/null.
This follows unicorn commit a6077391bb62d0b13016084b0eea36b987afe8f0
Thanks to Dan Moore for suggesting it on the unicorn list.
A few more minor changes, more memory reduction changes coming...
* proxy_pass: no point in closing StringIO
* proxy_pass: allow filtering or overriding response headers
* support SIGWINCH even if not daemonized
* use Unicorn::HttpParser#response_start_sent accessor
* reduce inline constant cache overheads
* proxy_pass: skip tests if kcar is missing
* ensure body is closed during hijack
|
|
Middlewares such as Rack::Lock (used by Rails) break badly unless
the response body is closed on hijack, so we will close it to follow
the lead of other popular Rack servers.
While it's unclear if there's anybody using rack.hijack besides
yahns/proxy_pass we'll try to emulate the behavior of other servers
as much as possible.
ref: https://github.com/ngauthier/tubesock/issues/10
While we're at it, use DieIfUsed correctly in test_ssl.rb :x
|
|
We don't require kcar to be installed since most users will never
use the proxy_pass feature, but show an informative error in case
they want to test this functionality.
|
|
We can use symbols in the SleepyPenguin APIs to trade speed for
space in uncommon code paths.
|
|
We don't need to waste a valuable ivar slot on each socket
when we know unicorn already maintains this flag for us.
|
|
This has no effect for the (default) single process case with
no master/worker relationship as that does not support SIGWINCH.
Some process managers such as foreman and daemontools rely on
yahnsnot daemonizing, but we still want to be able to process
SIGWINCH in that case.
stdout and stderr may be redirected to a pipe (for cronolog or
similar process), so those are less likely to be attached to a TTY
than stdin. This also allows users to process SIGWINCH when running
inside a regular terminal if they redirect stdin to /dev/null.
This follows unicorn commit a6077391bb62d0b13016084b0eea36b987afe8f0
Thanks to Dan Moore for suggesting it on the unicorn list.
|
|
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.
|
|
It does not release memory immediately and GC can handle it
reliably, so don't waste a constant lookup + cache entry on it.
While we're at it, explain why we can't do a simpler
respond_to? check instead.
|
|
Nothing really significant, so there's no need to upgrade if
you're not affected by the minor fixes and changes in this
release.
For all users, LoadError and SyntaxError exceptions are now
logged and non-fatal within worker threads serving application
code. Thanks to Lin Jen-Shin <godfat@godfat.org> for bringing
this up on the mailing list.
Additionally, temporary files buffered to the filesystem will
now support the Rack::TempfileReaper middleware in rack 1.6+
For rack.hijack users, there are some changes and improvements.
rack.hijack should return a usable IO-like object for SSL users,
now. The rack.input object is no longer closed on hijacking,
allowing apps to continue using buffered input after hijacking.
There is also a bugfix for the rare apps which hijack requests
after emitting 100-continue responses.
Note: there is also a work-in-progress and under-documented
asynchronous Yayns::ProxyPass Rack app which uses rack.hijack
internally. This will allow yahns to act as a fully-buffering
reverse proxy to upstream servers which cannot handle slow
clients. Yahns::ProxyPass NOT production-ready as of this
release. The old, synchronous extras/proxy_pass.rb code
remains usable.
There's also the usual round of minor code bloat reduction.
|
|
We'll have to support both, it seems.
|
|
Some applications may lazily load code during app dispatch,
triggering LoadError or SyntaxError exceptions. Log the error and
backtrace so application maintainers can more easily notice and
diagnose problems.
Keep in mind users are likely to have performance and race condition
problems with lazy loading, and the process may still be in a bad
state due to partially-loaded code. This commit is only intended to
give application authors a chance to notice and fix or avoid
problems in the future.
Note: logging fatal exceptions by default in all threads was
proposed in ruby-core, but currently not implemented in any released
version:
https://bugs.ruby-lang.org/issues/6647
Reported-by: Lin Jen-Shin (godfat) <godfat@godfat.org>
<CAA2_N1umJO12XH9r+JHnA6r=z=Mwp_PqOrdnW65oqW2K2-iAoQ@mail.gmail.com>
|
|
Rack::TempfileReaper was added in rack 1.6 to cleanup temporary
files. Make Yahns::TmpIO ducktype-compatible and put it into
env['rack.tempfiles'] array so Rack::TempfileReaper may be used to
free up space used by temporary buffer files.
ref: commit 3bdf5481e49d76b4502c51e5bdd93f68bfd1f0b4 in unicorn
|
|
Arrays are less verbose, but they have more bytecode overhead
which actually matters at runtime.
|
|
We do not want rack.hijack users relying on kgio_* methods since
kgio is trying to make itself obsolete (as Ruby itself adopts
kgio features). This is a bit wonky since our common case tries
to minimize object allocation by only using the Kgio::Socket
derived class.
|
|
OpenSSL::SSL::SSLSocket does not actually respond to a shutdown
method, and it would not be safe to call anyways. Merely
shutdown at the OS level and let any handling thread clean it
up.
|
|
Proc object allocation is not cheap, so avoid doing it and
allow Yahns::HttpClient to be assigned as the
env["rack.hijack"] callback for Rack applications.
|
|
No point in wasting space and reducing code clarity with this
method to remove references to live objects.
|
|
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).
|
|
Bad connections or dead upstreams cannot be solved looking at a
backtrace, so avoid polluting logs with them and making other
problems less visible.
|
|
We don't need optimized dispatch methods in cold code, so use
the more space-efficient "nil?" method dispatch to save us one
word per-call site for a rough total of 24 bytes saving.
|
|
We may set binary mode upon open by passing "b" in the mode string,
so avoid the extra method dispatch and bytecode/cache overhead that
entails.
|
|
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.
|
|
Upstreams may shut us down while we're writing a request body,
attempt to forward any responses from the upstream back to the
client which may explain the rejection reason for giant uploads.
|
|
This giant method needs to spew an error response in case uploads
to the upstream fail, ensure the local variable is defined early.
|
|
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.
|
|
Since yahns/proxy_pass is not a drop-in replacement, reinstate
the old, synchronous version to avoid breaking existing setups
which require Rack middleware support.
|
|
kgio_writev returns nil on success instead of the number of bytes
written, so we must manually calculate the number of bytes written
intead :x
This is triggerable when sending giant chunked responses.
|
|
A dumb string comparison will do here, so there's no point
in paying the memory and CPU cost of a regexp match when we
already extracted the suffix from a header key.
|
|
hijack seems incompatible with many middlewares, so return a
wonky response tuplet just in case...
|
|
Instance variable attr methods are cheaper and we can shove the
complexity down to tmpio by allowing it to accept a nil argument
for the temporary directory.
This adds 4 bytes to tmpio but removes over 1K in http_context
on a 32-bit system.
|
|
Literal regexps costs over 400 bytes of memory on x86-64 per-site,
so there's no point in using them to cause bloat at cold call sites
where runtime performance does not matter.
|
|
On one of my ancient, cache-starved home systems, this tends to
cause pathologically bad performance during some of these tests.
|
|
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.
|
|
Pipelining uploads is rare in practice, but they must behave
properly in case some brave soul wants to start doing it.
|
|
Even if a response is screwed, we need to ensure breakage is
predictable.
|
|
We were incorrectly stashing the return value of detach_rbuf!
into the inter-thread buffer buffer which is bound to the client.
|
|
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).
|
|
This allows us to write chunked response bodies without extra
copying to clients which support streaming.
|
|
We'll be supporting "un-hijacking" a client socket for proxy_pass
and we must preserve state for pipelined requests.
|
|
This should make it easier to track state for asynchronous
proxy_pass buffering.
|
|
This will allow us to write arrays for chunked output without
unnecessary data copies.
|
|
This is no longer a part of the "extras" section
|
|
This is probably the least useful tuning knob and may be removed
in the future; so at least warn users about it.
ref: <CAA2_N1t_UJZO9Vu9vx3NxysCx+okip8_pM+NpwhHrExM+f1e+Q@mail.gmail.com>
|
|
We will support "un-hijacking", so the repeated ep_insert/ep_remove
sequences in the kernel will get expensive and complicated for our
user-land code, too.
|
|
This will rely on rack.hijack in the future to support
asynchronous execution without tying up a thread when waiting
for upstreams. For now, this allows simpler code with fewer
checks and the use of monotonic time on newer versions of Ruby.
|
|
Of course, some users will prefer to bind HTTP application
servers to Unix domain sockets for better isolation and (maybe)
better performance.
|
|
This is slightly more nginx-style behavior and allows simpler
configuration.
|