HTTP/[23]: continue upload when state.drain is set

- as reported in #10433, HTTP/2 uploads may stall when a response is
  received before the upload is done. This happens when the
  data->state.drain is set for such a transfer, as the special handling
  in transfer.c from then on only cared about downloads.
- add continuation of uploads, if applicable, in this case.
- add pytest case test_07_12_upload_seq_large to reproduce this scenario
  (although, current nghttp2 implementation is using drain less often)

Reported-by: Lucas Pardue

Fixes #10433
Closes #10443
This commit is contained in:
Stefan Eissing 2023-02-08 10:26:58 +01:00 committed by Daniel Stenberg
parent 8c762f5998
commit 3de3ea6a64
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
4 changed files with 55 additions and 22 deletions

View file

@ -592,11 +592,12 @@ char *curl_pushheader_byname(struct curl_pushheaders *h, const char *header)
static void drained_transfer(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_h2_ctx *ctx = cf->ctx;
DEBUGASSERT(ctx->drain_total >= data->state.drain);
ctx->drain_total -= data->state.drain;
data->state.drain = 0;
if(data->state.drain) {
struct cf_h2_ctx *ctx = cf->ctx;
DEBUGASSERT(ctx->drain_total > 0);
ctx->drain_total--;
data->state.drain = 0;
}
}
/*
@ -605,11 +606,12 @@ static void drained_transfer(struct Curl_cfilter *cf,
static void drain_this(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_h2_ctx *ctx = cf->ctx;
data->state.drain++;
ctx->drain_total++;
DEBUGASSERT(ctx->drain_total >= data->state.drain);
if(!data->state.drain) {
struct cf_h2_ctx *ctx = cf->ctx;
data->state.drain = 1;
ctx->drain_total++;
DEBUGASSERT(ctx->drain_total > 0);
}
}
static struct Curl_easy *h2_duphandle(struct Curl_cfilter *cf,
@ -1575,8 +1577,6 @@ static ssize_t http2_handle_stream_close(struct Curl_cfilter *cf,
}
}
DEBUGASSERT(data->state.drain == 0);
/* Reset to FALSE to prevent infinite loop in readwrite_data function. */
stream->closed = FALSE;
if(stream->error == NGHTTP2_REFUSED_STREAM) {
@ -1929,8 +1929,9 @@ static ssize_t cf_h2_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
drain_this(cf, data);
Curl_expire(data, 0, EXPIRE_RUN_NOW);
}
else
else {
drained_transfer(cf, data);
}
nread = retlen;
DEBUGF(LOG_CF(data, cf, "[h2sid=%u] cf_h2_recv -> %zd",

View file

@ -1080,6 +1080,8 @@ CURLcode Curl_readwrite(struct connectdata *conn,
if(data->state.drain) {
select_res |= CURL_CSELECT_IN;
DEBUGF(infof(data, "Curl_readwrite: forcibly told to drain data"));
if((k->keepon & KEEP_SENDBITS) == KEEP_SEND)
select_res |= CURL_CSELECT_OUT;
}
#endif