quiche: do not iterate over multi handles

Quiche needs to find easy handles to events. Do this by iterating
over the filters stream hash and lookup the easy handle on a match.

This O(+streams-in-filter) vs O(all easy handles), at least once
we fix the multi lookup to use a hash.

Closes #16607
This commit is contained in:
Stefan Eissing 2025-03-07 11:34:40 +01:00 committed by Daniel Stenberg
parent 906aa04d9b
commit 646ffb591a
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2

View File

@ -196,24 +196,66 @@ static void h3_stream_hash_free(curl_off_t id, void *stream)
h3_stream_ctx_free((struct stream_ctx *)stream);
}
static void check_resumes(struct Curl_cfilter *cf,
struct Curl_easy *data)
typedef bool cf_quiche_svisit(struct Curl_cfilter *cf,
struct Curl_easy *sdata,
struct stream_ctx *stream,
void *user_data);
struct cf_quiche_visit_ctx {
struct Curl_cfilter *cf;
struct Curl_multi *multi;
cf_quiche_svisit *cb;
void *user_data;
};
static bool cf_quiche_stream_do(curl_off_t mid, void *val, void *user_data)
{
struct cf_quiche_visit_ctx *vctx = user_data;
struct stream_ctx *stream = val;
struct Curl_easy *sdata = Curl_multi_get_handle(vctx->multi, mid);
if(sdata)
return vctx->cb(vctx->cf, sdata, stream, vctx->user_data);
return TRUE;
}
static void cf_quiche_for_all_streams(struct Curl_cfilter *cf,
struct Curl_multi *multi,
cf_quiche_svisit *do_cb,
void *user_data)
{
struct cf_quiche_ctx *ctx = cf->ctx;
struct Curl_llist_node *e;
struct cf_quiche_visit_ctx vctx;
vctx.cf = cf;
vctx.multi = multi;
vctx.cb = do_cb;
vctx.user_data = user_data;
Curl_hash_offt_visit(&ctx->streams, cf_quiche_stream_do, &vctx);
}
DEBUGASSERT(data->multi);
for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) {
struct Curl_easy *sdata = Curl_node_elem(e);
if(sdata->conn == data->conn) {
struct stream_ctx *stream = H3_STREAM_CTX(ctx, sdata);
if(stream && stream->quic_flow_blocked) {
stream->quic_flow_blocked = FALSE;
Curl_expire(data, 0, EXPIRE_RUN_NOW);
CURL_TRC_CF(data, cf, "[%"FMT_PRIu64"] unblock", stream->id);
}
}
static bool cf_quiche_do_resume(struct Curl_cfilter *cf,
struct Curl_easy *sdata,
struct stream_ctx *stream,
void *user_data)
{
(void)user_data;
if(stream->quic_flow_blocked) {
stream->quic_flow_blocked = FALSE;
Curl_expire(sdata, 0, EXPIRE_RUN_NOW);
CURL_TRC_CF(sdata, cf, "[%"FMT_PRIu64"] unblock", stream->id);
}
return TRUE;
}
static bool cf_quiche_do_expire(struct Curl_cfilter *cf,
struct Curl_easy *sdata,
struct stream_ctx *stream,
void *user_data)
{
(void)stream;
(void)user_data;
CURL_TRC_CF(sdata, cf, "conn closed, expire transfer");
Curl_expire(sdata, 0, EXPIRE_RUN_NOW);
return TRUE;
}
static CURLcode h3_data_setup(struct Curl_cfilter *cf,
@ -285,52 +327,12 @@ static void h3_drain_stream(struct Curl_cfilter *cf,
}
}
static struct Curl_easy *get_stream_easy(struct Curl_cfilter *cf,
struct Curl_easy *data,
curl_uint64_t stream_id,
struct stream_ctx **pstream)
{
struct cf_quiche_ctx *ctx = cf->ctx;
struct stream_ctx *stream;
(void)cf;
stream = H3_STREAM_CTX(ctx, data);
if(stream && stream->id == stream_id) {
*pstream = stream;
return data;
}
else {
struct Curl_llist_node *e;
DEBUGASSERT(data->multi);
for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) {
struct Curl_easy *sdata = Curl_node_elem(e);
if(sdata->conn != data->conn)
continue;
stream = H3_STREAM_CTX(ctx, sdata);
if(stream && stream->id == stream_id) {
*pstream = stream;
return sdata;
}
}
}
*pstream = NULL;
return NULL;
}
static void cf_quiche_expire_conn_closed(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct Curl_llist_node *e;
DEBUGASSERT(data->multi);
CURL_TRC_CF(data, cf, "conn closed, expire all transfers");
for(e = Curl_llist_head(&data->multi->process); e; e = Curl_node_next(e)) {
struct Curl_easy *sdata = Curl_node_elem(e);
if(sdata == data || sdata->conn != data->conn)
continue;
CURL_TRC_CF(sdata, cf, "conn closed, expire transfer");
Curl_expire(sdata, 0, EXPIRE_RUN_NOW);
}
cf_quiche_for_all_streams(cf, data->multi, cf_quiche_do_expire, NULL);
}
/*
@ -557,14 +559,48 @@ static CURLcode h3_process_event(struct Curl_cfilter *cf,
return result;
}
static CURLcode cf_quiche_ev_process(struct Curl_cfilter *cf,
struct Curl_easy *data,
struct stream_ctx *stream,
quiche_h3_event *ev)
{
CURLcode result = h3_process_event(cf, data, stream, ev);
h3_drain_stream(cf, data);
if(result)
CURL_TRC_CF(data, cf, "error processing event %s "
"for [%"FMT_PRIu64"] -> %d", cf_ev_name(ev),
stream->id, result);
return result;
}
struct cf_quich_disp_ctx {
curl_uint64_t stream_id;
struct Curl_cfilter *cf;
struct Curl_multi *multi;
quiche_h3_event *ev;
CURLcode result;
};
static bool cf_quiche_disp_event(curl_off_t mid, void *val, void *user_data)
{
struct cf_quich_disp_ctx *dctx = user_data;
struct stream_ctx *stream = val;
if(stream->id == dctx->stream_id) {
struct Curl_easy *sdata = Curl_multi_get_handle(dctx->multi, mid);
if(sdata)
dctx->result = cf_quiche_ev_process(dctx->cf, sdata, stream, dctx->ev);
return FALSE; /* stop iterating */
}
return TRUE;
}
static CURLcode cf_poll_events(struct Curl_cfilter *cf,
struct Curl_easy *data)
{
struct cf_quiche_ctx *ctx = cf->ctx;
struct stream_ctx *stream = NULL;
struct Curl_easy *sdata;
quiche_h3_event *ev;
CURLcode result;
/* Take in the events and distribute them to the transfers. */
while(ctx->h3c) {
@ -576,28 +612,27 @@ static CURLcode cf_poll_events(struct Curl_cfilter *cf,
CURL_TRC_CF(data, cf, "error poll: %"FMT_PRId64, stream3_id);
return CURLE_HTTP3;
}
sdata = get_stream_easy(cf, data, stream3_id, &stream);
if(!sdata || !stream) {
CURL_TRC_CF(data, cf, "discard event %s for unknown [%"FMT_PRId64"]",
cf_ev_name(ev), stream3_id);
}
else {
result = h3_process_event(cf, sdata, stream, ev);
h3_drain_stream(cf, sdata);
if(result) {
CURL_TRC_CF(data, cf, "error processing event %s "
"for [%"FMT_PRIu64"] -> %d", cf_ev_name(ev),
stream3_id, result);
if(data == sdata) {
/* Only report this error to the caller if it is about the
* transfer we were called with. Otherwise we fail a transfer
* due to a problem in another one. */
quiche_h3_event_free(ev);
struct cf_quich_disp_ctx dctx;
dctx.stream_id = (curl_uint64_t)stream3_id;
dctx.cf = cf;
dctx.multi = data->multi;
dctx.ev = ev;
dctx.result = CURLE_OK;
stream = H3_STREAM_CTX(ctx, data);
if(stream && stream->id == dctx.stream_id) {
/* event for calling transfer */
CURLcode result = cf_quiche_ev_process(cf, data, stream, ev);
quiche_h3_event_free(ev);
if(result)
return result;
}
}
quiche_h3_event_free(ev);
else {
/* another transfer, do not return errors, as they are not for
* the calling transfer */
Curl_hash_offt_visit(&ctx->streams, cf_quiche_disp_event, &dctx);
quiche_h3_event_free(ev);
}
}
}
return CURLE_OK;
@ -686,7 +721,8 @@ static CURLcode cf_process_ingress(struct Curl_cfilter *cf,
if(rctx.pkts > 0) {
/* quiche digested ingress packets. It might have opened flow control
* windows again. */
check_resumes(cf, data);
DEBUGASSERT(data->multi);
cf_quiche_for_all_streams(cf, data->multi, cf_quiche_do_resume, NULL);
}
return cf_poll_events(cf, data);
}