#include "curl_memory.h"
#include "memdebug.h"
-#define H2_NW_CHUNK_SIZE (128*1024)
-#define PROXY_H2_NW_RECV_CHUNKS 1
+#define H2_CHUNK_SIZE (16*1024)
+
+#define PROXY_HTTP2_HUGE_WINDOW_SIZE (100 * 1024 * 1024)
+#define H2_TUNNEL_WINDOW_SIZE (10 * 1024 * 1024)
+
+#define PROXY_H2_NW_RECV_CHUNKS (H2_TUNNEL_WINDOW_SIZE / H2_CHUNK_SIZE)
#define PROXY_H2_NW_SEND_CHUNKS 1
-#define PROXY_HTTP2_HUGE_WINDOW_SIZE (32 * 1024 * 1024) /* 32 MB */
+#define H2_TUNNEL_RECV_CHUNKS (H2_TUNNEL_WINDOW_SIZE / H2_CHUNK_SIZE)
+#define H2_TUNNEL_SEND_CHUNKS ((128 * 1024) / H2_CHUNK_SIZE)
-#define H2_TUNNEL_WINDOW_SIZE (1024 * 1024)
-#define H2_TUNNEL_CHUNK_SIZE (32 * 1024)
-#define H2_TUNNEL_RECV_CHUNKS \
- (H2_TUNNEL_WINDOW_SIZE / H2_TUNNEL_CHUNK_SIZE)
-#define H2_TUNNEL_SEND_CHUNKS \
- (H2_TUNNEL_WINDOW_SIZE / H2_TUNNEL_CHUNK_SIZE)
typedef enum {
H2_TUNNEL_INIT, /* init/default/no tunnel state */
char *authority;
int32_t stream_id;
uint32_t error;
+ size_t upload_blocked_len;
h2_tunnel_state state;
- bool has_final_response;
- bool closed;
- bool reset;
+ BIT(has_final_response);
+ BIT(closed);
+ BIT(reset);
};
static CURLcode tunnel_stream_init(struct Curl_cfilter *cf,
ts->state = H2_TUNNEL_INIT;
ts->stream_id = -1;
- Curl_bufq_init2(&ts->recvbuf, H2_TUNNEL_CHUNK_SIZE, H2_TUNNEL_RECV_CHUNKS,
+ Curl_bufq_init2(&ts->recvbuf, H2_CHUNK_SIZE, H2_TUNNEL_RECV_CHUNKS,
BUFQ_OPT_SOFT_LIMIT);
- Curl_bufq_init(&ts->sendbuf, H2_TUNNEL_CHUNK_SIZE, H2_TUNNEL_SEND_CHUNKS);
+ Curl_bufq_init(&ts->sendbuf, H2_CHUNK_SIZE, H2_TUNNEL_SEND_CHUNKS);
if(cf->conn->bits.conn_to_host)
hostname = cf->conn->conn_to_host.name;
int32_t last_stream_id;
BIT(conn_closed);
BIT(goaway);
+ BIT(nw_out_blocked);
};
/* How to access `call_data` from a cf_h2 filter */
DEBUGASSERT(!ctx->h2);
memset(&ctx->tunnel, 0, sizeof(ctx->tunnel));
- Curl_bufq_init(&ctx->inbufq, H2_NW_CHUNK_SIZE, PROXY_H2_NW_RECV_CHUNKS);
- Curl_bufq_init(&ctx->outbufq, H2_NW_CHUNK_SIZE, PROXY_H2_NW_SEND_CHUNKS);
+ Curl_bufq_init(&ctx->inbufq, H2_CHUNK_SIZE, PROXY_H2_NW_RECV_CHUNKS);
+ Curl_bufq_init(&ctx->outbufq, H2_CHUNK_SIZE, PROXY_H2_NW_SEND_CHUNKS);
if(tunnel_stream_init(cf, &ctx->tunnel))
goto out;
return result;
}
+static int should_close_session(struct cf_h2_proxy_ctx *ctx)
+{
+ return !nghttp2_session_want_read(ctx->h2) &&
+ !nghttp2_session_want_write(ctx->h2);
+}
+
static CURLcode proxy_h2_nw_out_flush(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
- size_t buflen = Curl_bufq_len(&ctx->outbufq);
ssize_t nwritten;
CURLcode result;
(void)data;
- if(!buflen)
+ if(Curl_bufq_is_empty(&ctx->outbufq))
return CURLE_OK;
- DEBUGF(LOG_CF(data, cf, "h2 conn flush %zu bytes", buflen));
nwritten = Curl_bufq_pass(&ctx->outbufq, proxy_h2_nw_out_writer, cf,
&result);
if(nwritten < 0) {
+ if(result == CURLE_AGAIN) {
+ DEBUGF(LOG_CF(data, cf, "flush nw send buffer(%zu) -> EAGAIN",
+ Curl_bufq_len(&ctx->outbufq)));
+ ctx->nw_out_blocked = 1;
+ }
return result;
}
- if((size_t)nwritten < buflen) {
- return CURLE_AGAIN;
- }
- return CURLE_OK;
+ DEBUGF(LOG_CF(data, cf, "nw send buffer flushed"));
+ return Curl_bufq_is_empty(&ctx->outbufq)? CURLE_OK: CURLE_AGAIN;
}
/*
return CURLE_OK;
}
-/*
- * Check if there's been an update in the priority /
- * dependency settings and if so it submits a PRIORITY frame with the updated
- * info.
- * Flush any out data pending in the network buffer.
- */
static CURLcode proxy_h2_progress_egress(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
int rv = 0;
- rv = nghttp2_session_send(ctx->h2);
+ ctx->nw_out_blocked = 0;
+ while(!rv && !ctx->nw_out_blocked && nghttp2_session_want_write(ctx->h2))
+ rv = nghttp2_session_send(ctx->h2);
+
if(nghttp2_is_fatal(rv)) {
DEBUGF(LOG_CF(data, cf, "nghttp2_session_send error (%s)%d",
nghttp2_strerror(rv), rv));
result = proxy_h2_progress_ingress(cf, data);
if(!result)
result = proxy_h2_progress_egress(cf, data);
- if(result) {
+ if(result && result != CURLE_AGAIN) {
h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
break;
}
}
result = proxy_h2_progress_egress(cf, data);
- if(result) {
+ if(result && result != CURLE_AGAIN) {
*err = result;
nread = -1;
}
static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf,
struct Curl_easy *data,
- const void *mem, size_t len, CURLcode *err)
+ const void *buf, size_t len, CURLcode *err)
{
struct cf_h2_proxy_ctx *ctx = cf->ctx;
struct cf_call_data save;
- ssize_t nwritten = -1;
- const unsigned char *buf = mem;
- size_t start_len = len;
int rv;
+ ssize_t nwritten;
+ CURLcode result;
+ int blocked = 0;
if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
*err = CURLE_SEND_ERROR;
}
CF_DATA_SAVE(save, cf, data);
- while(len) {
+ if(ctx->tunnel.closed) {
+ nwritten = -1;
+ *err = CURLE_SEND_ERROR;
+ goto out;
+ }
+ else if(ctx->tunnel.upload_blocked_len) {
+ /* the data in `buf` has alread been submitted or added to the
+ * buffers, but have been EAGAINed on the last invocation. */
+ DEBUGASSERT(len >= ctx->tunnel.upload_blocked_len);
+ if(len < ctx->tunnel.upload_blocked_len) {
+ /* Did we get called again with a smaller `len`? This should not
+ * happend. We are not prepared to handle that. */
+ failf(data, "HTTP/2 proxy, send again with decreased length");
+ *err = CURLE_HTTP2;
+ nwritten = -1;
+ goto out;
+ }
+ nwritten = (ssize_t)ctx->tunnel.upload_blocked_len;
+ ctx->tunnel.upload_blocked_len = 0;
+ }
+ else {
nwritten = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, err);
- if(nwritten <= 0) {
- if(*err && *err != CURLE_AGAIN) {
- DEBUGF(LOG_CF(data, cf, "error adding data to tunnel sendbuf: %d",
- *err));
- nwritten = -1;
+ if(nwritten < 0) {
+ if(*err != CURLE_AGAIN)
goto out;
- }
- /* blocked */
nwritten = 0;
}
- else {
- DEBUGASSERT((size_t)nwritten <= len);
- buf += (size_t)nwritten;
- len -= (size_t)nwritten;
- }
+ }
- /* resume the tunnel stream and let the h2 session send, which
- * triggers reading from tunnel.sendbuf */
+ if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
+ /* req body data is buffered, resume the potentially suspended stream */
rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id);
if(nghttp2_is_fatal(rv)) {
*err = CURLE_SEND_ERROR;
nwritten = -1;
goto out;
}
- *err = proxy_h2_progress_egress(cf, data);
- if(*err) {
- nwritten = -1;
- goto out;
- }
-
- if(!nwritten && Curl_bufq_is_full(&ctx->tunnel.sendbuf)) {
- size_t rwin;
- /* we could not add to the buffer and after session processing,
- * it is still full. */
- rwin = nghttp2_session_get_stream_remote_window_size(
- ctx->h2, ctx->tunnel.stream_id);
- DEBUGF(LOG_CF(data, cf, "cf_send: tunnel win %u/%zu",
- nghttp2_session_get_remote_window_size(ctx->h2), rwin));
- if(rwin == 0) {
- /* We cannot upload more as the stream's remote window size
- * is 0. We need to receive WIN_UPDATEs before we can continue.
- */
- data->req.keepon |= KEEP_SEND_HOLD;
- DEBUGF(LOG_CF(data, cf, "pausing send as remote flow "
- "window is exhausted"));
- }
- break;
- }
}
- nwritten = start_len - len;
- if(nwritten > 0) {
- *err = CURLE_OK;
+ /* Call the nghttp2 send loop and flush to write ALL buffered data,
+ * headers and/or request body completely out to the network */
+ result = proxy_h2_progress_egress(cf, data);
+ if(result == CURLE_AGAIN) {
+ blocked = 1;
}
- else if(ctx->tunnel.closed) {
+ else if(result) {
+ *err = result;
nwritten = -1;
- *err = CURLE_SEND_ERROR;
+ goto out;
}
- else {
- nwritten = -1;
+ else if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
+ /* although we wrote everything that nghttp2 wants to send now,
+ * there is data left in our stream send buffer unwritten. This may
+ * be due to the stream's HTTP/2 flow window being exhausted. */
+ blocked = 1;
+ }
+
+ if(blocked) {
+ /* Unable to send all data, due to connection blocked or H2 window
+ * exhaustion. Data is left in our stream buffer, or nghttp2's internal
+ * frame buffer or our network out buffer. */
+ size_t rwin = nghttp2_session_get_stream_remote_window_size(
+ ctx->h2, ctx->tunnel.stream_id);
+ if(rwin == 0) {
+ /* H2 flow window exhaustion.
+ * FIXME: there is no way to HOLD all transfers that use this
+ * proxy connection AND to UNHOLD all of them again when the
+ * window increases.
+ * We *could* iterate over all data on this conn maybe? */
+ DEBUGF(LOG_CF(data, cf, "[h2sid=%d] remote flow "
+ "window is exhausted", ctx->tunnel.stream_id));
+ }
+
+ /* Whatever the cause, we need to return CURL_EAGAIN for this call.
+ * We have unwritten state that needs us being invoked again and EAGAIN
+ * is the only way to ensure that. */
+ ctx->tunnel.upload_blocked_len = nwritten;
+ DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send(len=%zu) BLOCK: win %u/%zu "
+ "blocked_len=%zu",
+ ctx->tunnel.stream_id, len,
+ nghttp2_session_get_remote_window_size(ctx->h2), rwin,
+ nwritten));
*err = CURLE_AGAIN;
+ nwritten = -1;
+ goto out;
+ }
+ else if(should_close_session(ctx)) {
+ /* nghttp2 thinks this session is done. If the stream has not been
+ * closed, this is an error state for out transfer */
+ if(ctx->tunnel.closed) {
+ *err = CURLE_SEND_ERROR;
+ nwritten = -1;
+ }
+ else {
+ DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session"));
+ *err = CURLE_HTTP2;
+ nwritten = -1;
+ }
}
out:
- DEBUGF(LOG_CF(data, cf, "cf_send(len=%zu) -> %zd, %d ",
- start_len, nwritten, *err));
+ DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send(len=%zu) -> %zd, %d, "
+ "h2 windows %d-%d (stream-conn), "
+ "buffers %zu-%zu (stream-conn)",
+ ctx->tunnel.stream_id, len, nwritten, *err,
+ nghttp2_session_get_stream_remote_window_size(
+ ctx->h2, ctx->tunnel.stream_id),
+ nghttp2_session_get_remote_window_size(ctx->h2),
+ Curl_bufq_len(&ctx->tunnel.sendbuf),
+ Curl_bufq_len(&ctx->outbufq)));
CF_DATA_RESTORE(cf, save);
return nwritten;
}
BIT(conn_closed);
BIT(goaway);
BIT(enable_push);
+ BIT(nw_out_blocked);
};
/* How to access `call_data` from a cf_h2 filter */
struct bufq sendbuf; /* request buffer */
struct dynhds resp_trailers; /* response trailer fields */
size_t resp_hds_len; /* amount of response header bytes in recvbuf */
+ size_t upload_blocked_len;
curl_off_t upload_left; /* number of request bytes left to upload */
char **push_headers; /* allocated array */
(void)cf;
bits = CURL_CSELECT_IN;
- if(!stream->send_closed && stream->upload_left)
+ if(!stream->send_closed &&
+ (stream->upload_left || stream->upload_blocked_len))
bits |= CURL_CSELECT_OUT;
if(data->state.dselect_bits != bits) {
+ DEBUGF(LOG_CF(data, cf, "[h2sid=%d] DRAIN dselect_bits=%x",
+ stream->id, bits));
data->state.dselect_bits = bits;
Curl_expire(data, 0, EXPIRE_RUN_NOW);
}
if(Curl_bufq_is_empty(&ctx->outbufq))
return CURLE_OK;
- DEBUGF(LOG_CF(data, cf, "h2 conn flush %zu bytes",
- Curl_bufq_len(&ctx->outbufq)));
nwritten = Curl_bufq_pass(&ctx->outbufq, nw_out_writer, cf, &result);
- if(nwritten < 0 && result != CURLE_AGAIN) {
+ if(nwritten < 0) {
+ if(result == CURLE_AGAIN) {
+ DEBUGF(LOG_CF(data, cf, "flush nw send buffer(%zu) -> EAGAIN",
+ Curl_bufq_len(&ctx->outbufq)));
+ ctx->nw_out_blocked = 1;
+ }
return result;
}
- return CURLE_OK;
+ DEBUGF(LOG_CF(data, cf, "nw send buffer flushed"));
+ return Curl_bufq_is_empty(&ctx->outbufq)? CURLE_OK: CURLE_AGAIN;
}
/*
nw_out_writer, cf, &result);
if(nwritten < 0) {
if(result == CURLE_AGAIN) {
+ ctx->nw_out_blocked = 1;
return NGHTTP2_ERR_WOULDBLOCK;
}
failf(data, "Failed sending HTTP2 data");
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
- if(!nwritten)
+ if(!nwritten) {
+ ctx->nw_out_blocked = 1;
return NGHTTP2_ERR_WOULDBLOCK;
-
+ }
return nwritten;
}
goto out;
}
- while(!rv && nghttp2_session_want_write(ctx->h2))
+ ctx->nw_out_blocked = 0;
+ while(!rv && !ctx->nw_out_blocked && nghttp2_session_want_write(ctx->h2))
rv = nghttp2_session_send(ctx->h2);
out:
out:
result = h2_progress_egress(cf, data);
- if(result) {
+ if(result && result != CURLE_AGAIN) {
*err = result;
nread = -1;
}
static ssize_t cf_h2_send(struct Curl_cfilter *cf, struct Curl_easy *data,
const void *buf, size_t len, CURLcode *err)
{
- /*
- * Currently, we send request in this function, but this function is also
- * used to send request body. It would be nice to add dedicated function for
- * request.
- */
struct cf_h2_ctx *ctx = cf->ctx;
struct stream_ctx *stream = H2_STREAM_CTX(data);
struct cf_call_data save;
int rv;
ssize_t nwritten;
CURLcode result;
+ int blocked = 0;
CF_DATA_SAVE(save, cf, data);
nwritten = http2_handle_stream_close(cf, data, stream, err);
goto out;
}
- /* If stream_id != -1, we have dispatched request HEADERS, and now
- are going to send or sending request body in DATA frame */
- nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err);
- if(nwritten < 0) {
- if(*err != CURLE_AGAIN)
+ else if(stream->upload_blocked_len) {
+ /* the data in `buf` has alread been submitted or added to the
+ * buffers, but have been EAGAINed on the last invocation. */
+ DEBUGASSERT(len >= stream->upload_blocked_len);
+ if(len < stream->upload_blocked_len) {
+ /* Did we get called again with a smaller `len`? This should not
+ * happend. We are not prepared to handle that. */
+ failf(data, "HTTP/2 send again with decreased length");
+ *err = CURLE_HTTP2;
+ nwritten = -1;
goto out;
- nwritten = 0;
+ }
+ nwritten = (ssize_t)stream->upload_blocked_len;
+ stream->upload_blocked_len = 0;
+ }
+ else {
+ /* If stream_id != -1, we have dispatched request HEADERS, and now
+ are going to send or sending request body in DATA frame */
+ nwritten = Curl_bufq_write(&stream->sendbuf, buf, len, err);
+ if(nwritten < 0) {
+ if(*err != CURLE_AGAIN)
+ goto out;
+ nwritten = 0;
+ }
}
- DEBUGF(LOG_CF(data, cf, "[h2sid=%u] bufq_write(len=%zu) -> %zd, %d",
- stream->id, len, nwritten, *err));
if(!Curl_bufq_is_empty(&stream->sendbuf)) {
+ /* req body data is buffered, resume the potentially suspended stream */
rv = nghttp2_session_resume_data(ctx->h2, stream->id);
if(nghttp2_is_fatal(rv)) {
*err = CURLE_SEND_ERROR;
goto out;
}
}
-
- result = h2_progress_ingress(cf, data);
- if(result) {
- *err = result;
- nwritten = -1;
- goto out;
- }
-
- result = h2_progress_egress(cf, data);
- if(result) {
- *err = result;
- nwritten = -1;
- goto out;
- }
-
- if(should_close_session(ctx)) {
- if(stream->closed) {
- nwritten = http2_handle_stream_close(cf, data, stream, err);
- }
- else {
- DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session"));
- *err = CURLE_HTTP2;
- nwritten = -1;
- }
- goto out;
- }
-
- if(!nwritten) {
- size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2,
- stream->id);
- DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send: win %u/%zu",
- stream->id,
- nghttp2_session_get_remote_window_size(ctx->h2), rwin));
- if(rwin == 0) {
- /* We cannot upload more as the stream's remote window size
- * is 0. We need to receive WIN_UPDATEs before we can continue.
- */
- data->req.keepon |= KEEP_SEND_HOLD;
- DEBUGF(LOG_CF(data, cf, "[h2sid=%d] holding send as remote flow "
- "window is exhausted", stream->id));
- }
- nwritten = -1;
- *err = CURLE_AGAIN;
- }
- /* handled writing BODY for open stream. */
- goto out;
}
else {
nwritten = h2_submit(&stream, cf, data, buf, len, err);
if(nwritten < 0) {
goto out;
}
+ DEBUGASSERT(stream);
+ }
- result = h2_progress_ingress(cf, data);
- if(result) {
- *err = result;
- nwritten = -1;
- goto out;
+ /* Call the nghttp2 send loop and flush to write ALL buffered data,
+ * headers and/or request body completely out to the network */
+ result = h2_progress_egress(cf, data);
+ if(result == CURLE_AGAIN) {
+ blocked = 1;
+ }
+ else if(result) {
+ *err = result;
+ nwritten = -1;
+ goto out;
+ }
+ else if(!Curl_bufq_is_empty(&stream->sendbuf)) {
+ /* although we wrote everything that nghttp2 wants to send now,
+ * there is data left in our stream send buffer unwritten. This may
+ * be due to the stream's HTTP/2 flow window being exhausted. */
+ blocked = 1;
+ }
+
+ if(blocked) {
+ /* Unable to send all data, due to connection blocked or H2 window
+ * exhaustion. Data is left in our stream buffer, or nghttp2's internal
+ * frame buffer or our network out buffer. */
+ size_t rwin = nghttp2_session_get_stream_remote_window_size(ctx->h2,
+ stream->id);
+ if(rwin == 0) {
+ /* H2 flow window exhaustion. We need to HOLD upload until we get
+ * a WINDOW_UPDATE from the server. */
+ data->req.keepon |= KEEP_SEND_HOLD;
+ DEBUGF(LOG_CF(data, cf, "[h2sid=%d] holding send as remote flow "
+ "window is exhausted", stream->id));
+ }
+
+ /* Whatever the cause, we need to return CURL_EAGAIN for this call.
+ * We have unwritten state that needs us being invoked again and EAGAIN
+ * is the only way to ensure that. */
+ stream->upload_blocked_len = nwritten;
+ DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send(len=%zu) BLOCK: win %u/%zu "
+ "blocked_len=%zu",
+ stream->id, len,
+ nghttp2_session_get_remote_window_size(ctx->h2), rwin,
+ nwritten));
+ *err = CURLE_AGAIN;
+ nwritten = -1;
+ goto out;
+ }
+ else if(should_close_session(ctx)) {
+ /* nghttp2 thinks this session is done. If the stream has not been
+ * closed, this is an error state for out transfer */
+ if(stream->closed) {
+ nwritten = http2_handle_stream_close(cf, data, stream, err);
}
-
- result = h2_progress_egress(cf, data);
- if(result) {
- *err = result;
+ else {
+ DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session"));
+ *err = CURLE_HTTP2;
nwritten = -1;
- goto out;
- }
-
- if(should_close_session(ctx)) {
- if(stream->closed) {
- nwritten = http2_handle_stream_close(cf, data, stream, err);
- }
- else {
- DEBUGF(LOG_CF(data, cf, "send: nothing to do in this session"));
- *err = CURLE_HTTP2;
- nwritten = -1;
- }
- goto out;
}
-
}
out:
if(stream) {
DEBUGF(LOG_CF(data, cf, "[h2sid=%d] cf_send(len=%zu) -> %zd, %d, "
- "buffered=%zu, upload_left=%zu, stream-window=%d, "
- "connection-window=%d",
+ "upload_left=%" CURL_FORMAT_CURL_OFF_T ", "
+ "h2 windows %d-%d (stream-conn), "
+ "buffers %zu-%zu (stream-conn)",
stream->id, len, nwritten, *err,
- Curl_bufq_len(&stream->sendbuf),
(ssize_t)stream->upload_left,
nghttp2_session_get_stream_remote_window_size(
ctx->h2, stream->id),
- nghttp2_session_get_remote_window_size(ctx->h2)));
- drain_stream(cf, data, stream);
+ nghttp2_session_get_remote_window_size(ctx->h2),
+ Curl_bufq_len(&stream->sendbuf),
+ Curl_bufq_len(&ctx->outbufq)));
}
else {
DEBUGF(LOG_CF(data, cf, "cf_send(len=%zu) -> %zd, %d, "
- "connection-window=%d",
+ "connection-window=%d, nw_send_buffer(%zu)",
len, nwritten, *err,
- nghttp2_session_get_remote_window_size(ctx->h2)));
+ nghttp2_session_get_remote_window_size(ctx->h2),
+ Curl_bufq_len(&ctx->outbufq)));
}
CF_DATA_RESTORE(cf, save);
return nwritten;
DEBUGASSERT(data);
if(ctx && ctx->h2 && stream) {
uint32_t window = pause? 0 : stream->local_window_size;
- CURLcode result;
int rv = nghttp2_session_set_local_window_size(ctx->h2,
NGHTTP2_FLAG_NONE,
if(!pause)
drain_stream(cf, data, stream);
- /* make sure the window update gets sent */
- result = h2_progress_egress(cf, data);
- if(result)
- return result;
+ /* attempt to send the window update */
+ (void)h2_progress_egress(cf, data);
if(!pause) {
/* Unpausing a h2 transfer, requires it to be run again. The server