/* A stream window is the maximum amount we need to buffer for
* each active transfer.
* Chunk size is large enough to take a full DATA frame */
-#define H3_STREAM_WINDOW_SIZE (64 * 1024)
-#define H3_STREAM_CHUNK_SIZE (16 * 1024)
+#define H3_STREAM_WINDOW_SIZE_INITIAL (32 * 1024)
+#define H3_STREAM_WINDOW_SIZE_MAX (10 * 1024 * 1024)
+#define H3_CONN_WINDOW_SIZE_MAX (100 * H3_STREAM_WINDOW_SIZE_MAX)
+
+#define H3_STREAM_CHUNK_SIZE (64 * 1024)
#if H3_STREAM_CHUNK_SIZE < NGTCP2_MAX_UDP_PAYLOAD_SIZE
#error H3_STREAM_CHUNK_SIZE smaller than NGTCP2_MAX_UDP_PAYLOAD_SIZE
#endif
* The benefit of the pool is that stream buffer to not keep
* spares. Memory consumption goes down when streams run empty,
* have a large upload done, etc. */
-#define H3_STREAM_POOL_SPARES \
- (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE) / 2
+#define H3_STREAM_POOL_SPARES 2
/* Receive and Send max number of chunks just follows from the
* chunk size and window size */
#define H3_STREAM_SEND_CHUNKS \
- (H3_STREAM_WINDOW_SIZE / H3_STREAM_CHUNK_SIZE)
+ (H3_STREAM_WINDOW_SIZE_MAX / H3_STREAM_CHUNK_SIZE)
/*
* Store ngtcp2 version info in this buffer.
struct bufc_pool stream_bufcp; /* chunk pool for streams */
struct dynbuf scratch; /* temp buffer for header construction */
struct uint_hash streams; /* hash `data->mid` to `h3_stream_ctx` */
- size_t max_stream_window; /* max flow window for one stream */
uint64_t used_bidi_streams; /* bidi streams we have opened */
uint64_t max_bidi_streams; /* max bidi streams we can open */
size_t earlydata_max; /* max amount of early data supported by
DEBUGASSERT(!ctx->initialized);
ctx->qlogfd = -1;
ctx->version = NGTCP2_PROTO_VER_MAX;
- ctx->max_stream_window = H3_STREAM_WINDOW_SIZE;
Curl_bufcp_init(&ctx->stream_bufcp, H3_STREAM_CHUNK_SIZE,
H3_STREAM_POOL_SPARES);
curlx_dyn_init(&ctx->scratch, CURL_MAX_HTTP_HEADER);
uint64_t error3; /* HTTP/3 stream error code */
curl_off_t upload_left; /* number of request bytes left to upload */
uint64_t download_unacked; /* bytes not acknowledged yet */
+ uint64_t window_size_max; /* max flow control window set for stream */
int status_code; /* HTTP status code */
CURLcode xfer_result; /* result from xfer_resp_write(_hd) */
BIT(resp_hds_complete); /* we have a complete, final response */
Curl_bufq_initp(&stream->sendbuf, &ctx->stream_bufcp,
H3_STREAM_SEND_CHUNKS, BUFQ_OPT_NONE);
stream->sendbuf_len_in_flight = 0;
+ stream->window_size_max = H3_STREAM_WINDOW_SIZE_INITIAL;
Curl_h1_req_parse_init(&stream->h1, H1_PARSE_DEFAULT_MAX_LINE_LEN);
if(!Curl_uint32_hash_set(&ctx->streams, data->mid, stream)) {
s->initial_ts = pktx->ts;
s->handshake_timeout = (data->set.connecttimeout > 0) ?
data->set.connecttimeout * NGTCP2_MILLISECONDS : QUIC_HANDSHAKE_TIMEOUT;
- s->max_window = 100 * ctx->max_stream_window;
- s->max_stream_window = ctx->max_stream_window;
+ s->max_window = H3_CONN_WINDOW_SIZE_MAX;
+ s->max_stream_window = 0; /* disable ngtcp2 auto-tuning of window */
s->no_pmtud = FALSE;
#ifdef NGTCP2_SETTINGS_V3
/* try ten times the ngtcp2 defaults here for problems with Caddy */
s->glitch_ratelim_burst = 1000 * 10;
s->glitch_ratelim_rate = 33 * 10;
#endif
- t->initial_max_data = 10 * ctx->max_stream_window;
- t->initial_max_stream_data_bidi_local = ctx->max_stream_window;
- t->initial_max_stream_data_bidi_remote = ctx->max_stream_window;
- t->initial_max_stream_data_uni = ctx->max_stream_window;
+ t->initial_max_data = s->max_window;
+ t->initial_max_stream_data_bidi_local = H3_STREAM_WINDOW_SIZE_INITIAL;
+ t->initial_max_stream_data_bidi_remote = H3_STREAM_WINDOW_SIZE_INITIAL;
+ t->initial_max_stream_data_uni = t->initial_max_data;
t->initial_max_streams_bidi = QUIC_MAX_STREAMS;
t->initial_max_streams_uni = QUIC_MAX_STREAMS;
t->max_idle_timeout = 0; /* no idle timeout from our side */
}
}
+static void cf_ngtcp2_stream_update_window(struct Curl_cfilter *cf,
+ struct Curl_easy *data,
+ struct h3_stream_ctx *stream)
+{
+ /* stream receive max window size for flow control. We can only
+ * grow it from the initial window size */
+ uint64_t swin_max = data->progress.dl.rlimit.rate_per_step ?
+ data->progress.dl.rlimit.rate_per_step : H3_STREAM_WINDOW_SIZE_MAX;
+ if(swin_max > stream->window_size_max) {
+ struct cf_ngtcp2_ctx *ctx = cf->ctx;
+ int rc = ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id,
+ swin_max - stream->window_size_max);
+ if(rc) {
+ CURL_TRC_CF(data, cf, "[%" PRId64 "] extend_max_stream_offset to %"
+ PRIu64 " -> %s (%d)",
+ stream->id, swin_max, ngtcp2_strerror(rc), rc);
+ DEBUGASSERT(0);
+ }
+ stream->window_size_max = swin_max;
+ }
+}
+
static void cf_ngtcp2_ack_stream(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct h3_stream_ctx *stream)
ngtcp2_conn_extend_max_stream_offset(ctx->qconn, stream->id, ack_len);
stream->download_unacked -= ack_len;
}
+
+ cf_ngtcp2_stream_update_window(cf, data, stream);
}
static int cb_h3_recv_data(nghttp3_conn *conn, int64_t stream3_id,
goto out;
}
+ cf_ngtcp2_stream_update_window(cf, data, stream);
+
if(Curl_trc_is_verbose(data)) {
infof(data, "[HTTP/3] [%" PRId64 "] OPENED stream for %s",
stream->id, Curl_bufref_ptr(&data->state.url));