BUG/MEDIUM: h2: debug incoming traffic in h2_wake()

Even after previous commit ("BUG/MEDIUM: h2: work around a connection
API limitation") there is still a problem with some requests. Sometimes
when polling for more request data while some pending data lies in the
buffer, there's no way to enter h2_recv() because the FD is not marked
ready for reading.

We need to slightly change the approach and make h2_recv() only receive
from the buffer and h2_wake() always attempt to demux if the demux is not
blocked.

However, if the connection is already being polled for reading, it will
not wake up from polling. For this reason we need to cheat and also
pretend a request for sending data, which ensures that as soon as any
direction may move, we can continue to demux. This shows that in the
long term we probably need a better way to resume an interrupted
operation at the mux level.

With this fix, no more hangups happen during uploads. Note that this
time the setup required to provoke the hangups was a bit complex :
  - client is "curl" running on local host, uploading 1.7 MB of
    data via haproxy
  - haproxy running on local host, forwarding to a remote server
    through a 100 Mbps only switch
  - timeouts disabled on haproxy
  - remote server made of thttpd executing a cgi reading request data
    through "dd bs=10" to slow down everything.

With such a setup, around 3-5% of the connections would hang up.

This fix needs to be backported to 1.8.
This commit is contained in:
Willy Tarreau 2017-12-14 10:34:52 +01:00
parent 6042aeb1e8
commit d13bf27e78

View File

@ -2094,15 +2094,6 @@ static void h2_recv(struct connection *conn)
if (buf->i == buf->size) if (buf->i == buf->size)
h2c->flags |= H2_CF_DEM_DFULL; h2c->flags |= H2_CF_DEM_DFULL;
h2_process_demux(h2c);
/* after streams have been processed, we should have made some room */
if (h2c->st0 >= H2_CS_ERROR || conn->flags & CO_FL_ERROR)
buf->i = 0;
if (buf->i != buf->size)
h2c->flags &= ~H2_CF_DEM_DFULL;
return; return;
} }
@ -2174,6 +2165,16 @@ static int h2_wake(struct connection *conn)
{ {
struct h2c *h2c = conn->mux_ctx; struct h2c *h2c = conn->mux_ctx;
if (h2c->dbuf->i && !(h2c->flags & H2_CF_DEM_BLOCK_ANY)) {
h2_process_demux(h2c);
if (h2c->st0 >= H2_CS_ERROR || conn->flags & CO_FL_ERROR)
h2c->dbuf->i = 0;
if (h2c->dbuf->i != h2c->dbuf->size)
h2c->flags &= ~H2_CF_DEM_DFULL;
}
/* /*
* If we received early data, try to wake any stream, just in case * If we received early data, try to wake any stream, just in case
* at least one of them was waiting for the handshake * at least one of them was waiting for the handshake
@ -2313,8 +2314,10 @@ static void h2_update_poll(struct conn_stream *cs)
if (cs->flags & CS_FL_DATA_RD_ENA) { if (cs->flags & CS_FL_DATA_RD_ENA) {
/* the stream indicates it's willing to read */ /* the stream indicates it's willing to read */
h2s->h2c->flags &= ~H2_CF_DEM_SFULL; h2s->h2c->flags &= ~H2_CF_DEM_SFULL;
if (h2s->h2c->dsi == h2s->id) if (h2s->h2c->dsi == h2s->id) {
conn_xprt_want_recv(cs->conn); conn_xprt_want_recv(cs->conn);
conn_xprt_want_send(cs->conn);
}
} }
/* Note: the stream and stream-int code doesn't allow us to perform a /* Note: the stream and stream-int code doesn't allow us to perform a