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.
This commit is contained in:
Willy Tarreau 2017-11-10 11:42:33 +01:00
parent aa39860aef
commit 22cf59bbba

View File

@ -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