From 22cf59bbbac90573cc28bf1035a3dae046083236 Mon Sep 17 00:00:00 2001 From: Willy Tarreau Date: Fri, 10 Nov 2017 11:42:33 +0100 Subject: [PATCH] BUG/MEDIUM: h2: support orphaned streams When a stream_interface performs a shutw() then a shutr(), the stream is marked closed. Then cs_destroy() calls h2_detach() and it cannot fail since we're on the leaving path of the caller. The problem is that in order to close streams we usually have to send either an emty DATA frame with the ES flag set or an RST_STREAM frame, and the mux buffer might already be full, forcing the stream to be queued. The forced removal of this stream causes this last message to silently disappear, and the client to wait forever for a response. This commit ensures we can detach the conn_stream from the h2 stream if the stream is blocked, effectively making the h2 stream an orphan, ensures that the mux can deal with orphaned streams after processing them, and that the demux can kill them upon receipt of GOAWAY. --- src/mux_h2.c | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/mux_h2.c b/src/mux_h2.c index a5cd89be7..d06a234f6 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -984,14 +984,21 @@ static void h2_wake_some_streams(struct h2c *h2c, int last, uint32_t flags) if (h2s->id <= last) break; node = eb32_next(node); - if (h2s->cs) { - h2s->cs->flags |= flags; - /* recv is used to force to detect CS_FL_EOS that wake() - * doesn't handle in the stream int code. - */ - h2s->cs->data_cb->recv(h2s->cs); - h2s->cs->data_cb->wake(h2s->cs); + + if (!h2s->cs) { + /* this stream was already orphaned */ + eb32_delete(&h2s->by_id); + pool_free2(pool2_h2s, h2s); + continue; } + + h2s->cs->flags |= flags; + /* recv is used to force to detect CS_FL_EOS that wake() + * doesn't handle in the stream int code. + */ + h2s->cs->data_cb->recv(h2s->cs); + h2s->cs->data_cb->wake(h2s->cs); + if (flags & CS_FL_ERROR && h2s->st < H2_SS_ERROR) h2s->st = H2_SS_ERROR; else if (flags & CS_FL_EOS && h2s->st == H2_SS_OPEN) @@ -1884,6 +1891,11 @@ static int h2_process_mux(struct h2c *h2c) LIST_INIT(&h2s->list); if (h2s->cs) h2s->cs->flags &= ~CS_FL_DATA_WR_ENA; + else { + /* just sent the last frame for this orphaned stream */ + eb32_delete(&h2s->by_id); + pool_free2(pool2_h2s, h2s); + } } } } @@ -1921,6 +1933,11 @@ static int h2_process_mux(struct h2c *h2c) LIST_INIT(&h2s->list); if (h2s->cs) h2s->cs->flags &= ~CS_FL_DATA_WR_ENA; + else { + /* just sent the last frame for this orphaned stream */ + eb32_delete(&h2s->by_id); + pool_free2(pool2_h2s, h2s); + } } } @@ -2237,6 +2254,12 @@ static void h2_detach(struct conn_stream *cs) h2c = h2s->h2c; h2s->cs = NULL; + /* this stream may be blocked waiting for some data to leave (possibly + * an ES or RST frame), so orphan it in this case. + */ + if (h2s->flags & (H2_SF_BLK_MBUSY | H2_SF_BLK_MROOM | H2_SF_BLK_MFCTL)) + return; + if ((h2c->flags & H2_CF_DEM_BLOCK_ANY && h2s->id == h2c->dsi) || (h2c->flags & H2_CF_MUX_BLOCK_ANY && h2s->id == h2c->msi)) { /* unblock the connection if it was blocked on this