From d7dd7e9ae4c2110f753cac02fd702eefef9fce16 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 10 Apr 2026 21:42:18 +0400 Subject: HTTP/3: optimize encoder stream memory usage Previously, the encoder stream allocated each new inserted field in the connection pool. This memory was not freed until the end of the connection. Now a special insert buffer is used for all inserts. --- src/http/v3/ngx_http_v3_parse.c | 30 +++++++++++++++++++++++++++--- src/http/v3/ngx_http_v3_parse.h | 1 + src/http/v3/ngx_http_v3_table.c | 22 ++++++++++++++++++++++ src/http/v3/ngx_http_v3_table.h | 2 ++ 4 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c index bcbf0dbe1..1ba08c791 100644 --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -633,9 +633,23 @@ ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, st->huffstate = 0; } - st->last = ngx_pnalloc(c->pool, n + 1); - if (st->last == NULL) { - return NGX_ERROR; + if (st->buf) { + if ((size_t) (st->buf->end - st->buf->last) < n + 1) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "not enough dynamic table capacity"); + + st->last = NULL; + return NGX_ERROR; + } + + st->last = st->buf->last; + st->buf->last += n + 1; + + } else { + st->last = ngx_pnalloc(c->pool, n + 1); + if (st->last == NULL) { + return NGX_ERROR; + } } st->value.data = st->last; @@ -1486,6 +1500,11 @@ ngx_http_v3_parse_field_inr(ngx_connection_t *c, ch = *b->pos; + st->literal.buf = ngx_http_v3_get_insert_buffer(c); + if (st->literal.buf == NULL) { + return NGX_ERROR; + } + st->dynamic = (ch & 0x40) ? 0 : 1; st->state = sw_name_index; @@ -1590,6 +1609,11 @@ ngx_http_v3_parse_field_iln(ngx_connection_t *c, ch = *b->pos; + st->literal.buf = ngx_http_v3_get_insert_buffer(c); + if (st->literal.buf == NULL) { + return NGX_ERROR; + } + st->literal.huffman = (ch & 0x20) ? 1 : 0; st->state = sw_name_len; diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h index ba004db5d..c7e1e1ebb 100644 --- a/src/http/v3/ngx_http_v3_parse.h +++ b/src/http/v3/ngx_http_v3_parse.h @@ -51,6 +51,7 @@ typedef struct { ngx_str_t value; u_char *last; u_char huffstate; + ngx_buf_t *buf; } ngx_http_v3_parse_literal_t; diff --git a/src/http/v3/ngx_http_v3_table.c b/src/http/v3/ngx_http_v3_table.c index 428e7326b..3b0b7b309 100644 --- a/src/http/v3/ngx_http_v3_table.c +++ b/src/http/v3/ngx_http_v3_table.c @@ -155,6 +155,28 @@ static ngx_http_v3_field_t ngx_http_v3_static_table[] = { }; +ngx_buf_t * +ngx_http_v3_get_insert_buffer(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->insert_buffer == NULL) { + dt->insert_buffer = ngx_create_temp_buf(c->pool, dt->capacity); + if (dt->insert_buffer == NULL) { + return NULL; + } + } + + dt->insert_buffer->last = dt->insert_buffer->pos; + + return dt->insert_buffer; +} + + ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value) diff --git a/src/http/v3/ngx_http_v3_table.h b/src/http/v3/ngx_http_v3_table.h index 1c2fb17b9..6644723d1 100644 --- a/src/http/v3/ngx_http_v3_table.h +++ b/src/http/v3/ngx_http_v3_table.h @@ -29,11 +29,13 @@ typedef struct { uint64_t insert_count; uint64_t ack_insert_count; ngx_event_t send_insert_count; + ngx_buf_t *insert_buffer; } ngx_http_v3_dynamic_table_t; void ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev); void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c); +ngx_buf_t *ngx_http_v3_get_insert_buffer(ngx_connection_t *c); ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *value); ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, -- cgit