From 95e9a7a88bc0859e2f8a767beee96554b72e80bb Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Tue, 18 Apr 2017 13:12:38 +0000 Subject: [PATCH] On the 2.4.x branch: Merged /httpd/httpd/trunk:r1790850,1790855,1791377,1791388,1791669,1791773 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1791781 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 13 +++ modules/http2/h2.h | 2 + modules/http2/h2_headers.c | 16 ++++ modules/http2/h2_headers.h | 6 ++ modules/http2/h2_mplx.c | 34 ++++++-- modules/http2/h2_mplx.h | 1 + modules/http2/h2_request.c | 14 ++-- modules/http2/h2_session.c | 70 ++++++++++------ modules/http2/h2_stream.c | 2 +- modules/http2/h2_util.c | 166 +++++++++++++++++++++++++------------ modules/http2/h2_util.h | 16 ++-- modules/http2/h2_version.h | 4 +- modules/http2/h2_workers.c | 1 + 13 files changed, 242 insertions(+), 103 deletions(-) diff --git a/CHANGES b/CHANGES index 47e759792b8..02b6e1753a2 100644 --- a/CHANGES +++ b/CHANGES @@ -2,6 +2,19 @@ Changes with Apache 2.4.26 + *) mod_http2: MaxKeepAliveRequests now limits the number of times a + slave connection gets reused. [Stefan Eissing] + + *) mod_http2: client streams that lack the EOF flag get now forcefully + closed with a RST_STREAM (NO_ERROR) when the request has been answered. + [Stefan Eissing] + + *) mod_http2: only when 'HttpProtocolOptions Unsafe' is configured, will + control characters in response headers or trailers be forwarded to the + client. Otherwise, in the default configuration, a request will eiher + fail with status 500 or the stream will be reset by a RST_STREAM frame. + [Stefan Eissing] + *) mod_brotli: Add a new module for dynamic Brotli (RFC 7932) compression. [Evgeny Kotkov] diff --git a/modules/http2/h2.h b/modules/http2/h2.h index df809fd4113..ad6979c1827 100644 --- a/modules/http2/h2.h +++ b/modules/http2/h2.h @@ -154,5 +154,7 @@ typedef int h2_stream_pri_cmp(int stream_id1, int stream_id2, void *ctx); #define H2_TASK_ID_NOTE "http2-task-id" #define H2_FILTER_DEBUG_NOTE "http2-debug" +#define H2_HDR_CONFORMANCE "http2-hdr-conformance" +#define H2_HDR_CONFORMANCE_UNSAFE "unsafe" #endif /* defined(__mod_h2__h2__) */ diff --git a/modules/http2/h2_headers.c b/modules/http2/h2_headers.c index 8add79f507d..ce7eaec2b3e 100644 --- a/modules/http2/h2_headers.c +++ b/modules/http2/h2_headers.c @@ -32,6 +32,12 @@ #include "h2_headers.h" +static int is_unsafe(server_rec *s) +{ + core_server_config *conf = ap_get_core_module_config(s->module_config); + return (conf->http_conformance == AP_HTTP_CONFORMANCE_UNSAFE); +} + typedef struct { apr_bucket_refcount refcount; h2_headers *headers; @@ -132,9 +138,19 @@ h2_headers *h2_headers_rcreate(request_rec *r, int status, headers->status = H2_ERR_HTTP_1_1_REQUIRED; } } + if (is_unsafe(r->server)) { + apr_table_setn(headers->notes, H2_HDR_CONFORMANCE, + H2_HDR_CONFORMANCE_UNSAFE); + } return headers; } +h2_headers *h2_headers_copy(apr_pool_t *pool, h2_headers *h) +{ + return h2_headers_create(h->status, apr_table_copy(pool, h->headers), + apr_table_copy(pool, h->notes), pool); +} + h2_headers *h2_headers_die(apr_status_t type, const h2_request *req, apr_pool_t *pool) { diff --git a/modules/http2/h2_headers.h b/modules/http2/h2_headers.h index 412e93fae25..95e99ee81ad 100644 --- a/modules/http2/h2_headers.h +++ b/modules/http2/h2_headers.h @@ -55,6 +55,12 @@ h2_headers *h2_headers_create(int status, apr_table_t *header, h2_headers *h2_headers_rcreate(request_rec *r, int status, apr_table_t *header, apr_pool_t *pool); +/** + * Clone the headers into another pool. This will not copy any + * header strings. + */ +h2_headers *h2_headers_copy(apr_pool_t *pool, h2_headers *h); + /** * Create the headers for the given error. * @param stream_id id of the stream to create the headers for diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c index 357bf5eaadf..e0704b7a888 100644 --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@ -158,13 +158,18 @@ h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent, apr_allocator_t *allocator; apr_thread_mutex_t *mutex; h2_mplx *m; + h2_ctx *ctx = h2_ctx_get(c, 0); ap_assert(conf); m = apr_pcalloc(parent, sizeof(h2_mplx)); if (m) { m->id = c->id; m->c = c; - + m->s = (ctx? h2_ctx_server_get(ctx) : NULL); + if (!m->s) { + m->s = c->base_server; + } + /* We create a pool with its own allocator to be used for * processing slave connections. This is the only way to have the * processing independant of its parent pool in the sense that it @@ -258,7 +263,17 @@ static int input_consumed_signal(h2_mplx *m, h2_stream *stream) static int report_consumption_iter(void *ctx, void *val) { - input_consumed_signal(ctx, val); + h2_stream *stream = val; + h2_mplx *m = ctx; + + input_consumed_signal(m, stream); + if (stream->state == H2_SS_CLOSED_L + && (!stream->task || stream->task->worker_done)) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, + H2_STRM_LOG(APLOGNO(10026), stream, "remote close missing")); + nghttp2_submit_rst_stream(stream->session->ngh2, NGHTTP2_FLAG_NONE, + stream->id, NGHTTP2_NO_ERROR); + } return 1; } @@ -276,8 +291,11 @@ static void task_destroy(h2_mplx *m, h2_task *task) int reuse_slave = 0; slave = task->c; - reuse_slave = ((m->spare_slaves->nelts < (m->limit_active * 3 / 2)) - && !task->rst_error); + + if (m->s->keep_alive_max == 0 || slave->keepalives < m->s->keep_alive_max) { + reuse_slave = ((m->spare_slaves->nelts < (m->limit_active * 3 / 2)) + && !task->rst_error); + } if (slave) { if (reuse_slave && slave->keepalive == AP_CONN_KEEPALIVE) { @@ -560,7 +578,10 @@ static apr_status_t out_close(h2_mplx *m, h2_task *task) if (!task) { return APR_ECONNABORTED; } - + if (task->c) { + ++task->c->keepalives; + } + stream = h2_ihash_get(m->streams, task->stream_id); if (!stream) { return APR_ECONNABORTED; @@ -703,8 +724,7 @@ static h2_task *next_stream_task(h2_mplx *m) } if (!stream->task) { - - m->c->keepalives++; + if (sid > m->max_stream_started) { m->max_stream_started = sid; } diff --git a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h index ed332c8bc3d..61b1b99aba7 100644 --- a/modules/http2/h2_mplx.h +++ b/modules/http2/h2_mplx.h @@ -57,6 +57,7 @@ struct h2_mplx { long id; conn_rec *c; apr_pool_t *pool; + server_rec *s; /* server for master conn */ unsigned int event_pending; unsigned int aborted; diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c index 7faf39340c2..dea676223d5 100644 --- a/modules/http2/h2_request.c +++ b/modules/http2/h2_request.c @@ -39,16 +39,15 @@ typedef struct { apr_table_t *headers; apr_pool_t *pool; + apr_status_t status; } h1_ctx; static int set_h1_header(void *ctx, const char *key, const char *value) { h1_ctx *x = ctx; - size_t klen = strlen(key); - if (!h2_req_ignore_header(key, klen)) { - h2_headers_add_h1(x->headers, x->pool, key, klen, value, strlen(value)); - } - return 1; + x->status = h2_req_add_header(x->headers, x->pool, key, strlen(key), + value, strlen(value)); + return (x->status == APR_SUCCESS)? 1 : 0; } apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, @@ -90,10 +89,11 @@ apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, x.pool = pool; x.headers = req->headers; + x.status = APR_SUCCESS; apr_table_do(set_h1_header, &x, r->headers_in, NULL); *preq = req; - return APR_SUCCESS; + return x.status; } apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, @@ -143,7 +143,7 @@ apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, } else { /* non-pseudo header, append to work bucket of stream */ - status = h2_headers_add_h1(req->headers, pool, name, nlen, value, vlen); + status = h2_req_add_header(req->headers, pool, name, nlen, value, vlen); } return status; diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index e23cb8d54b4..b22f7135b1b 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -792,13 +792,13 @@ static apr_status_t h2_session_create_int(h2_session **psession, return status; } - session->in_pending = h2_iq_create(session->pool, session->max_stream_count); + session->in_pending = h2_iq_create(session->pool, (int)session->max_stream_count); if (session->in_pending == NULL) { apr_pool_destroy(pool); return APR_ENOMEM; } - session->in_process = h2_iq_create(session->pool, session->max_stream_count); + session->in_process = h2_iq_create(session->pool, (int)session->max_stream_count); if (session->in_process == NULL) { apr_pool_destroy(pool); return APR_ENOMEM; @@ -1080,13 +1080,16 @@ struct h2_stream *h2_session_push(h2_session *session, h2_stream *is, { h2_stream *stream; h2_ngheader *ngh; - int nid; + apr_status_t status; + int nid = 0; - ngh = h2_util_ngheader_make_req(is->pool, push->req); - nid = nghttp2_submit_push_promise(session->ngh2, 0, is->id, - ngh->nv, ngh->nvlen, NULL); - if (nid <= 0) { - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, + status = h2_req_create_ngheader(&ngh, is->pool, push->req); + if (status == APR_SUCCESS) { + nid = nghttp2_submit_push_promise(session->ngh2, 0, is->id, + ngh->nv, ngh->nvlen, NULL); + } + if (status != APR_SUCCESS || nid <= 0) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, H2_STRM_LOG(APLOGNO(03075), is, "submitting push promise fail: %s"), nghttp2_strerror(nid)); return NULL; @@ -1280,16 +1283,25 @@ static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream, else if (stream->has_response) { h2_ngheader *nh; - nh = h2_util_ngheader_make(stream->pool, headers->headers); - ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, - H2_STRM_LOG(APLOGNO(03072), stream, "submit %d trailers"), (int)nh->nvlen); - rv = nghttp2_submit_trailer(session->ngh2, stream->id, nh->nv, nh->nvlen); + status = h2_res_create_ngtrailer(&nh, stream->pool, headers); + + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, + H2_STRM_LOG(APLOGNO(03072), stream, "submit %d trailers"), + (int)nh->nvlen); + if (status == APR_SUCCESS) { + rv = nghttp2_submit_trailer(session->ngh2, stream->id, + nh->nv, nh->nvlen); + } + else { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, + H2_STRM_LOG(APLOGNO(10024), stream, "invalid trailers")); + h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR); + } goto leave; } else { nghttp2_data_provider provider, *pprovider = NULL; h2_ngheader *ngh; - apr_table_t *hout; const char *note; ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, @@ -1335,17 +1347,16 @@ static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream, } h2_session_set_prio(session, stream, stream->pref_priority); - hout = headers->headers; note = apr_table_get(headers->notes, H2_FILTER_DEBUG_NOTE); if (note && !strcmp("on", note)) { int32_t connFlowIn, connFlowOut; connFlowIn = nghttp2_session_get_effective_local_window_size(session->ngh2); connFlowOut = nghttp2_session_get_remote_window_size(session->ngh2); - hout = apr_table_clone(stream->pool, hout); - apr_table_setn(hout, "conn-flow-in", + headers = h2_headers_copy(stream->pool, headers); + apr_table_setn(headers->headers, "conn-flow-in", apr_itoa(stream->pool, connFlowIn)); - apr_table_setn(hout, "conn-flow-out", + apr_table_setn(headers->headers, "conn-flow-out", apr_itoa(stream->pool, connFlowOut)); } @@ -1357,17 +1368,24 @@ static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream, goto leave; } - ngh = h2_util_ngheader_make_res(stream->pool, headers->status, hout); - rv = nghttp2_submit_response(session->ngh2, stream->id, - ngh->nv, ngh->nvlen, pprovider); - stream->has_response = h2_headers_are_response(headers); - session->have_written = 1; - - if (stream->initiated_on) { - ++session->pushes_submitted; + status = h2_res_create_ngheader(&ngh, stream->pool, headers); + if (status == APR_SUCCESS) { + rv = nghttp2_submit_response(session->ngh2, stream->id, + ngh->nv, ngh->nvlen, pprovider); + stream->has_response = h2_headers_are_response(headers); + session->have_written = 1; + + if (stream->initiated_on) { + ++session->pushes_submitted; + } + else { + ++session->responses_submitted; + } } else { - ++session->responses_submitted; + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, + H2_STRM_LOG(APLOGNO(10025), stream, "invalid response")); + h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR); } } diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c index 9784b4ec28d..4cd2132207e 100644 --- a/modules/http2/h2_stream.c +++ b/modules/http2/h2_stream.c @@ -1008,7 +1008,7 @@ apr_status_t h2_stream_in_consumed(h2_stream *stream, apr_off_t amount) apr_off_t consumed = amount; while (consumed > 0) { - int len = (consumed > INT_MAX)? INT_MAX : consumed; + int len = (consumed > INT_MAX)? INT_MAX : (int)consumed; nghttp2_session_consume(session->ngh2, stream->id, len); consumed -= len; } diff --git a/modules/http2/h2_util.c b/modules/http2/h2_util.c index 0ac65ccf656..f4c3aab3a9e 100644 --- a/modules/http2/h2_util.c +++ b/modules/http2/h2_util.c @@ -1382,89 +1382,150 @@ static int count_header(void *ctx, const char *key, const char *value) return 1; } -#define NV_ADD_LIT_CS(nv, k, v) add_header(nv, k, sizeof(k) - 1, v, strlen(v)) -#define NV_ADD_CS_CS(nv, k, v) add_header(nv, k, strlen(k), v, strlen(v)) +static const char *inv_field_name_chr(const char *token) +{ + const char *p = ap_scan_http_token(token); + if (p == token && *p == ':') { + p = ap_scan_http_token(++p); + } + return (p && *p)? p : NULL; +} -static int add_header(h2_ngheader *ngh, - const char *key, size_t key_len, - const char *value, size_t val_len) +static const char *inv_field_value_chr(const char *token) { - nghttp2_nv *nv = &ngh->nv[ngh->nvlen++]; - + const char *p = ap_scan_http_field_content(token); + return (p && *p)? p : NULL; +} + +typedef struct ngh_ctx { + apr_pool_t *p; + int unsafe; + h2_ngheader *ngh; + apr_status_t status; +} ngh_ctx; + +static int add_header(ngh_ctx *ctx, const char *key, const char *value) +{ + nghttp2_nv *nv = &(ctx->ngh)->nv[(ctx->ngh)->nvlen++]; + const char *p; + + if (!ctx->unsafe) { + if ((p = inv_field_name_chr(key))) { + ap_log_perror(APLOG_MARK, APLOG_TRACE1, APR_EINVAL, ctx->p, + "h2_request: head field '%s: %s' has invalid char %s", + key, value, p); + ctx->status = APR_EINVAL; + return 0; + } + if ((p = inv_field_value_chr(value))) { + ap_log_perror(APLOG_MARK, APLOG_TRACE1, APR_EINVAL, ctx->p, + "h2_request: head field '%s: %s' has invalid char %s", + key, value, p); + ctx->status = APR_EINVAL; + return 0; + } + } nv->name = (uint8_t*)key; - nv->namelen = key_len; + nv->namelen = strlen(key); nv->value = (uint8_t*)value; - nv->valuelen = val_len; + nv->valuelen = strlen(value); + return 1; } static int add_table_header(void *ctx, const char *key, const char *value) { if (!h2_util_ignore_header(key)) { - add_header(ctx, key, strlen(key), value, strlen(value)); + add_header(ctx, key, value); } return 1; } - -h2_ngheader *h2_util_ngheader_make(apr_pool_t *p, apr_table_t *header) +static apr_status_t ngheader_create(h2_ngheader **ph, apr_pool_t *p, + int unsafe, size_t key_count, + const char *keys[], const char *values[], + apr_table_t *headers) { - h2_ngheader *ngh; - size_t n; + ngh_ctx ctx; + size_t n, i; - n = 0; - apr_table_do(count_header, &n, header, NULL); + ctx.p = p; + ctx.unsafe = unsafe; - ngh = apr_pcalloc(p, sizeof(h2_ngheader)); - ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); - apr_table_do(add_table_header, ngh, header, NULL); + n = key_count; + apr_table_do(count_header, &n, headers, NULL); + + *ph = ctx.ngh = apr_pcalloc(p, sizeof(h2_ngheader)); + if (!ctx.ngh) { + return APR_ENOMEM; + } + + ctx.ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); + if (!ctx.ngh->nv) { + return APR_ENOMEM; + } + + ctx.status = APR_SUCCESS; + for (i = 0; i < key_count; ++i) { + if (!add_header(&ctx, keys[i], values[i])) { + return ctx.status; + } + } + + apr_table_do(add_table_header, &ctx, headers, NULL); - return ngh; + return ctx.status; } -h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, - int http_status, - apr_table_t *header) +static int is_unsafe(h2_headers *h) { - h2_ngheader *ngh; - size_t n; - - n = 1; - apr_table_do(count_header, &n, header, NULL); - - ngh = apr_pcalloc(p, sizeof(h2_ngheader)); - ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); - NV_ADD_LIT_CS(ngh, ":status", apr_psprintf(p, "%d", http_status)); - apr_table_do(add_table_header, ngh, header, NULL); + const char *v = apr_table_get(h->notes, H2_HDR_CONFORMANCE); + return (v && !strcmp(v, H2_HDR_CONFORMANCE_UNSAFE)); +} - return ngh; +apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p, + h2_headers *headers) +{ + return ngheader_create(ph, p, is_unsafe(headers), + 0, NULL, NULL, headers->headers); +} + +apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p, + h2_headers *headers) +{ + const char *keys[] = { + ":status" + }; + const char *values[] = { + apr_psprintf(p, "%d", headers->status) + }; + return ngheader_create(ph, p, is_unsafe(headers), + H2_ALEN(keys), keys, values, headers->headers); } -h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, - const struct h2_request *req) +apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p, + const struct h2_request *req) { - h2_ngheader *ngh; - size_t n; + const char *keys[] = { + ":scheme", + ":authority", + ":path", + ":method", + }; + const char *values[] = { + req->scheme, + req->authority, + req->path, + req->method, + }; - ap_assert(req); ap_assert(req->scheme); ap_assert(req->authority); ap_assert(req->path); ap_assert(req->method); - n = 4; - apr_table_do(count_header, &n, req->headers, NULL); - - ngh = apr_pcalloc(p, sizeof(h2_ngheader)); - ngh->nv = apr_pcalloc(p, n * sizeof(nghttp2_nv)); - NV_ADD_LIT_CS(ngh, ":scheme", req->scheme); - NV_ADD_LIT_CS(ngh, ":authority", req->authority); - NV_ADD_LIT_CS(ngh, ":path", req->path); - NV_ADD_LIT_CS(ngh, ":method", req->method); - apr_table_do(add_table_header, ngh, req->headers, NULL); - - return ngh; + return ngheader_create(ph, p, 0, H2_ALEN(keys), keys, values, req->headers); } /******************************************************************************* @@ -1481,7 +1542,6 @@ typedef struct { #define H2_LIT_ARGS(a) (a),H2_ALEN(a) static literal IgnoredRequestHeaders[] = { -/*H2_DEF_LITERAL("expect"),*/ H2_DEF_LITERAL("upgrade"), H2_DEF_LITERAL("connection"), H2_DEF_LITERAL("keep-alive"), @@ -1547,7 +1607,7 @@ int h2_res_ignore_trailer(const char *name, size_t len) return ignore_header(H2_LIT_ARGS(IgnoredResponseTrailers), name, len); } -apr_status_t h2_headers_add_h1(apr_table_t *headers, apr_pool_t *pool, +apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, const char *name, size_t nlen, const char *value, size_t vlen) { diff --git a/modules/http2/h2_util.h b/modules/http2/h2_util.h index 9b408fad3df..4b901bfe681 100644 --- a/modules/http2/h2_util.h +++ b/modules/http2/h2_util.h @@ -341,19 +341,21 @@ const char *h2_util_base64url_encode(const char *data, int h2_util_ignore_header(const char *name); +struct h2_headers; + typedef struct h2_ngheader { nghttp2_nv *nv; apr_size_t nvlen; } h2_ngheader; -h2_ngheader *h2_util_ngheader_make(apr_pool_t *p, apr_table_t *header); -h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, - int http_status, - apr_table_t *header); -h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, - const struct h2_request *req); +apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p, + struct h2_headers *headers); +apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p, + struct h2_headers *headers); +apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p, + const struct h2_request *req); -apr_status_t h2_headers_add_h1(apr_table_t *headers, apr_pool_t *pool, +apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, const char *name, size_t nlen, const char *value, size_t vlen); diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h index 528d21aed7d..72ac248c8d9 100644 --- a/modules/http2/h2_version.h +++ b/modules/http2/h2_version.h @@ -26,7 +26,7 @@ * @macro * Version number of the http2 module as c string */ -#define MOD_HTTP2_VERSION "1.10.1" +#define MOD_HTTP2_VERSION "1.10.2" /** * @macro @@ -34,7 +34,7 @@ * release. This is a 24 bit number with 8 bits for major number, 8 bits * for minor and 8 bits for patch. Version 1.2.3 becomes 0x010203. */ -#define MOD_HTTP2_VERSION_NUM 0x010a01 +#define MOD_HTTP2_VERSION_NUM 0x010a02 #endif /* mod_h2_h2_version_h */ diff --git a/modules/http2/h2_workers.c b/modules/http2/h2_workers.c index 9c7afc64e6c..0bbb65223f7 100644 --- a/modules/http2/h2_workers.c +++ b/modules/http2/h2_workers.c @@ -143,6 +143,7 @@ static void cleanup_zombies(h2_workers *workers) slot->thread = NULL; } apr_atomic_dec32(&workers->worker_count); + slot->next = NULL; push_slot(&workers->free, slot); } } -- 2.47.2