From 42891fce56ece06e41f39ceea1088b8e13898aeb Mon Sep 17 00:00:00 2001 From: Stefan Eissing Date: Fri, 18 Dec 2015 14:45:18 +0000 Subject: [PATCH] 3 new timeout configuration directives for mod_http2 git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1720801 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 7 ++- docs/manual/mod/mod_http2.xml | 94 +++++++++++++++++++++++++++++++++++ modules/http2/h2_config.c | 57 +++++++++++++++++++++ modules/http2/h2_config.h | 7 +++ modules/http2/h2_conn.c | 5 -- modules/http2/h2_filter.c | 79 +++++++++++++++++++++++++---- modules/http2/h2_filter.h | 20 ++++++-- modules/http2/h2_io.c | 65 ++++++++++++++++++++++++ modules/http2/h2_io.h | 21 +++++++- modules/http2/h2_mplx.c | 63 +++++++++++------------ modules/http2/h2_mplx.h | 1 + modules/http2/h2_session.c | 22 ++++++-- modules/http2/h2_session.h | 13 ++--- modules/http2/h2_version.h | 2 +- 14 files changed, 385 insertions(+), 71 deletions(-) diff --git a/CHANGES b/CHANGES index fca8455f06..669ce85c13 100644 --- a/CHANGES +++ b/CHANGES @@ -1,9 +1,14 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.0 + *) mod_http2: adding new config directives and the implementation behind + them: H2Timeout, H2KeepAliveTimeout, H2StreamTimeout. Documentation in + the http2 manual. + [Stefan Eissing] + *) mod_http2: when running in async mpm processing, mod_http2 will cease processing on idle connections (e.g. no open streams) back to the mpm. - [Stefan Eissing] + [Stefan Eissing] *) mod_http2: fixed bug in input window size calculation by moving chunked request body encoding into later stage of processing. diff --git a/docs/manual/mod/mod_http2.xml b/docs/manual/mod/mod_http2.xml index f28a0543b1..64156347b2 100644 --- a/docs/manual/mod/mod_http2.xml +++ b/docs/manual/mod/mod_http2.xml @@ -709,4 +709,98 @@ H2PushPriority text/css interleaved # weight 256 default + + H2Timeout + Timeout (in seconds) for HTTP/2 connections + H2Timeout seconds + H2Timeout 5 + + server config + virtual host + + Available in version 2.4.19 and later. + + +

+ This directive sets the timeout for read/write operations on + connections where HTTP/2 is negotiated. This can be used server wide or for specific + VirtualHosts. +

+

+ This directive is similar to the + Timeout, but + applies only to HTTP/2 connections. +

+

+ A value of 0 enforces no timeout. +

+
+
+ + + H2KeepAliveTimeout + Timeout (in seconds) for idle HTTP/2 connections + H2KeepAliveTimeout seconds + H2KeepAliveTimeout 300 + + server config + virtual host + + Available in version 2.4.19 and later. + + +

+ This directive sets the timeout for read/write operations on + idle connections where HTTP/2 is negotiated. This can be used server wide or for specific + VirtualHosts. +

+

+ This directive is similar to the + KeepAliveTimeout, but + applies only to HTTP/2 connections. A HTTP/2 connection is considered + idle when no streams are open, e.g. no requests are ongoing. +

+

+ A value of 0 enforces no timeout. +

+
+
+ + + H2StreamTimeout + Timeout (in seconds) for idle HTTP/2 connections + H2StreamTimeout seconds + H2StreamTimeout 120 + + server config + virtual host + + Available in version 2.4.19 and later. + + +

+ This directive sets the timeout for read/write operations on + HTTP/2 streams, e.g. individual requests. This can be used server wide or for specific + VirtualHosts. +

+

+ Due to the nature of HTTP/2, which sends multiple requests over a single + connection and has priority scheduling, individual streams might not + see input for much longer times than HTTP/1.1 requests would. +

+

+ A value of 0 enforces no timeout, so could wait on chances to receive + input or write data indefinitely. This expose a server to + risks of thread exhaustion. +

+

+ Depending on your handling of pushed streams, + priorities and general responsiveness, a site might need to increase + this value. For example, if you PUSH a large resource before + the requested one, the initial stream will not write until the + pushed resource is fully sent. +

+
+
+ diff --git a/modules/http2/h2_config.c b/modules/http2/h2_config.c index 6bbac83f71..75f6d71cd5 100644 --- a/modules/http2/h2_config.c +++ b/modules/http2/h2_config.c @@ -59,6 +59,9 @@ static h2_config defconf = { 1, /* TLS cooldown secs */ 1, /* HTTP/2 server push enabled */ NULL, /* map of content-type to priorities */ + 5, /* normal connection timeout */ + 5*60, /* idle connection timeout */ + 2*60, /* stream timeout */ }; static int files_per_session; @@ -122,6 +125,9 @@ static void *h2_config_create(apr_pool_t *pool, conf->tls_cooldown_secs = DEF_VAL; conf->h2_push = DEF_VAL; conf->priorities = NULL; + conf->h2_timeout = DEF_VAL; + conf->h2_keepalive = DEF_VAL; + conf->h2_stream_timeout = DEF_VAL; return conf; } @@ -167,6 +173,9 @@ void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv) else { n->priorities = add->priorities? add->priorities : base->priorities; } + n->h2_timeout = H2_CONFIG_GET(add, base, h2_timeout); + n->h2_keepalive = H2_CONFIG_GET(add, base, h2_keepalive); + n->h2_stream_timeout = H2_CONFIG_GET(add, base, h2_stream_timeout); return n; } @@ -214,6 +223,12 @@ apr_int64_t h2_config_geti64(const h2_config *conf, h2_config_var_t var) return H2_CONFIG_GET(conf, &defconf, tls_cooldown_secs); case H2_CONF_PUSH: return H2_CONFIG_GET(conf, &defconf, h2_push); + case H2_CONF_TIMEOUT_SECS: + return H2_CONFIG_GET(conf, &defconf, h2_timeout); + case H2_CONF_KEEPALIVE_SECS: + return H2_CONFIG_GET(conf, &defconf, h2_keepalive); + case H2_CONF_STREAM_TIMEOUT_SECS: + return H2_CONFIG_GET(conf, &defconf, h2_stream_timeout); default: return DEF_VAL; } @@ -511,6 +526,42 @@ static const char *h2_conf_set_tls_cooldown_secs(cmd_parms *parms, return NULL; } +static const char *h2_conf_set_timeout(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = (h2_config *)h2_config_sget(parms->server); + (void)arg; + cfg->h2_timeout = (int)apr_atoi64(value); + if (cfg->h2_timeout < 0) { + return "value must be >= 0"; + } + return NULL; +} + +static const char *h2_conf_set_keepalive(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = (h2_config *)h2_config_sget(parms->server); + (void)arg; + cfg->h2_keepalive = (int)apr_atoi64(value); + if (cfg->h2_keepalive < 0) { + return "value must be >= 0"; + } + return NULL; +} + +static const char *h2_conf_set_stream_timeout(cmd_parms *parms, + void *arg, const char *value) +{ + h2_config *cfg = (h2_config *)h2_config_sget(parms->server); + (void)arg; + cfg->h2_stream_timeout = (int)apr_atoi64(value); + if (cfg->h2_stream_timeout < 0) { + return "value must be >= 0"; + } + return NULL; +} + #define AP_END_CMD AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL) @@ -549,6 +600,12 @@ const command_rec h2_cmds[] = { RSRC_CONF, "off to disable HTTP/2 server push"), AP_INIT_TAKE23("H2PushPriority", h2_conf_add_push_priority, NULL, RSRC_CONF, "define priority of PUSHed resources per content type"), + AP_INIT_TAKE1("H2Timeout", h2_conf_set_timeout, NULL, + RSRC_CONF, "read/write timeout (seconds) for HTTP/2 connections"), + AP_INIT_TAKE1("H2KeepAliveTimeout", h2_conf_set_keepalive, NULL, + RSRC_CONF, "timeout (seconds) for idle HTTP/2 connections, no streams open"), + AP_INIT_TAKE1("H2StreamTimeout", h2_conf_set_stream_timeout, NULL, + RSRC_CONF, "read/write timeout (seconds) for HTTP/2 streams"), AP_END_CMD }; diff --git a/modules/http2/h2_config.h b/modules/http2/h2_config.h index 2c688e9c16..fab9b9fead 100644 --- a/modules/http2/h2_config.h +++ b/modules/http2/h2_config.h @@ -39,6 +39,9 @@ typedef enum { H2_CONF_TLS_WARMUP_SIZE, H2_CONF_TLS_COOLDOWN_SECS, H2_CONF_PUSH, + H2_CONF_TIMEOUT_SECS, + H2_CONF_KEEPALIVE_SECS, + H2_CONF_STREAM_TIMEOUT_SECS, } h2_config_var_t; struct apr_hash_t; @@ -65,6 +68,10 @@ typedef struct h2_config { int tls_cooldown_secs; /* Seconds of idle time before going back to small TLS records */ int h2_push; /* if HTTP/2 server push is enabled */ struct apr_hash_t *priorities;/* map of content-type to h2_priority records */ + + int h2_timeout; /* timeout for http/2 connections */ + int h2_keepalive; /* timeout for idle connections, no streams */ + int h2_stream_timeout; /* timeout for http/2 streams, slave connections */ } h2_config; diff --git a/modules/http2/h2_conn.c b/modules/http2/h2_conn.c index 54e8e405c6..5bc3d475ec 100644 --- a/modules/http2/h2_conn.c +++ b/modules/http2/h2_conn.c @@ -132,7 +132,6 @@ static module *h2_conn_mpm_module(void) { apr_status_t h2_conn_setup(h2_ctx *ctx, conn_rec *c, request_rec *r) { h2_session *session; - h2_filter_core_in *in; if (!workers) { ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911) @@ -149,10 +148,6 @@ apr_status_t h2_conn_setup(h2_ctx *ctx, conn_rec *c, request_rec *r) h2_ctx_session_set(ctx, session); - in = apr_pcalloc(session->pool, sizeof(*in)); - in->session = session; - ap_add_input_filter("H2_IN", in, r, c); - ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c); return APR_SUCCESS; diff --git a/modules/http2/h2_filter.c b/modules/http2/h2_filter.c index 37a6df3e74..85a8d87ed2 100644 --- a/modules/http2/h2_filter.c +++ b/modules/http2/h2_filter.c @@ -22,14 +22,15 @@ #include #include "h2_private.h" -#include "h2_session.h" #include "h2_conn_io.h" #include "h2_util.h" #include "h2_filter.h" +#define UNSET -1 +#define H2MIN(x,y) ((x) < (y) ? (x) : (y)) -static apr_status_t consume_brigade(h2_filter_core_in *in, +static apr_status_t consume_brigade(h2_filter_cin *cin, apr_bucket_brigade *bb, apr_read_type_e block) { @@ -51,13 +52,13 @@ static apr_status_t consume_brigade(h2_filter_core_in *in, if (status == APR_SUCCESS && bucket_length > 0) { apr_size_t consumed = 0; - status = h2_session_receive(in->session, bucket_data, - bucket_length, &consumed); + status = cin->cb(cin->cb_ctx, bucket_data, bucket_length, &consumed); if (status == APR_SUCCESS && bucket_length > consumed) { /* We have data left in the bucket. Split it. */ status = apr_bucket_split(bucket, consumed); } readlen += consumed; + cin->last_read = apr_time_now(); } } apr_bucket_delete(bucket); @@ -69,6 +70,38 @@ static apr_status_t consume_brigade(h2_filter_core_in *in, return status; } +static apr_status_t check_time_left(h2_filter_cin *cin, + apr_time_t *ptime_left) +{ + if (cin->timeout_secs > 0) { + *ptime_left = (cin->last_read + apr_time_from_sec(cin->timeout_secs) + - apr_time_now()); + if (*ptime_left <= 0) + return APR_TIMEUP; + + if (*ptime_left < apr_time_from_sec(1)) { + *ptime_left = apr_time_from_sec(1); + } + } + return APR_SUCCESS; +} + +h2_filter_cin *h2_filter_cin_create(apr_pool_t *p, h2_filter_cin_cb *cb, void *ctx) +{ + h2_filter_cin *cin; + + cin = apr_pcalloc(p, sizeof(*cin)); + cin->pool = p; + cin->cb = cb; + cin->cb_ctx = ctx; + cin->last_read = UNSET; + return cin; +} + +void h2_filter_cin_timeout_set(h2_filter_cin *cin, int timeout_secs) +{ + cin->timeout_secs = timeout_secs; +} apr_status_t h2_filter_core_input(ap_filter_t* f, apr_bucket_brigade* brigade, @@ -76,10 +109,12 @@ apr_status_t h2_filter_core_input(ap_filter_t* f, apr_read_type_e block, apr_off_t readbytes) { - h2_filter_core_in *in = f->ctx; + h2_filter_cin *cin = f->ctx; apr_status_t status = APR_SUCCESS; + apr_time_t saved_timeout = UNSET; + apr_time_t time_left = UNSET; - ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c, + ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c, "core_input: read, block=%d, mode=%d, readbytes=%ld", block, mode, (long)readbytes); @@ -91,32 +126,54 @@ apr_status_t h2_filter_core_input(ap_filter_t* f, return (block == APR_BLOCK_READ)? APR_SUCCESS : APR_EAGAIN; } - if (!f->bb) { - f->bb = apr_brigade_create(in->session->pool, f->c->bucket_alloc); + if (!cin->bb) { + cin->bb = apr_brigade_create(cin->pool, f->c->bucket_alloc); + } + + if (!cin->socket) { + cin->socket = ap_get_conn_socket(f->c); + cin->last_read = apr_time_now(); /* first call */ } - if (APR_BRIGADE_EMPTY(f->bb)) { + if (APR_BRIGADE_EMPTY(cin->bb)) { /* We only do a blocking read when we have no streams to process. So, * in httpd scoreboard lingo, we are in a KEEPALIVE connection state. * When reading non-blocking, we do have streams to process and update * child with NULL request. That way, any current request information * in the scoreboard is preserved. */ + status = check_time_left(cin, &time_left); + if (status != APR_SUCCESS) + goto out; + if (block == APR_BLOCK_READ) { ap_update_child_status_from_conn(f->c->sbh, SERVER_BUSY_KEEPALIVE, f->c); + if (time_left > 0) { + status = apr_socket_timeout_get(cin->socket, &saved_timeout); + AP_DEBUG_ASSERT(status == APR_SUCCESS); + status = apr_socket_timeout_set(cin->socket, H2MIN(time_left, saved_timeout)); + AP_DEBUG_ASSERT(status == APR_SUCCESS); + } } else { ap_update_child_status(f->c->sbh, SERVER_BUSY_READ, NULL); } - status = ap_get_brigade(f->next, f->bb, AP_MODE_READBYTES, + status = ap_get_brigade(f->next, cin->bb, AP_MODE_READBYTES, block, readbytes); } +out: switch (status) { case APR_SUCCESS: - return consume_brigade(in, f->bb, block); + if (saved_timeout != UNSET) { + apr_socket_timeout_set(cin->socket, saved_timeout); + } + status = consume_brigade(cin, cin->bb, block); + if (status == APR_SUCCESS) { + status = check_time_left(cin, &time_left); + } case APR_EOF: case APR_EAGAIN: break; diff --git a/modules/http2/h2_filter.h b/modules/http2/h2_filter.h index a9035b67fe..e27a82d7cc 100644 --- a/modules/http2/h2_filter.h +++ b/modules/http2/h2_filter.h @@ -18,9 +18,23 @@ struct h2_session; -typedef struct { - struct h2_session *session; -} h2_filter_core_in; +typedef apr_status_t h2_filter_cin_cb(void *ctx, + const char *data, apr_size_t len, + apr_size_t *readlen); + +typedef struct h2_filter_cin { + apr_pool_t *pool; + apr_bucket_brigade *bb; + h2_filter_cin_cb *cb; + void *cb_ctx; + apr_socket_t *socket; + int timeout_secs; + apr_time_t last_read; +} h2_filter_cin; + +h2_filter_cin *h2_filter_cin_create(apr_pool_t *p, h2_filter_cin_cb *cb, void *ctx); + +void h2_filter_cin_timeout_set(h2_filter_cin *cin, int timeout_secs); apr_status_t h2_filter_core_input(ap_filter_t* filter, apr_bucket_brigade* brigade, diff --git a/modules/http2/h2_io.c b/modules/http2/h2_io.c index d3880797ed..801106341d 100644 --- a/modules/http2/h2_io.c +++ b/modules/http2/h2_io.c @@ -15,13 +15,18 @@ #include +#include +#include + #include #include #include #include #include "h2_private.h" +#include "h2_h2.h" #include "h2_io.h" +#include "h2_mplx.h" #include "h2_response.h" #include "h2_request.h" #include "h2_task.h" @@ -95,6 +100,66 @@ apr_status_t h2_io_in_shutdown(h2_io *io) return h2_io_in_close(io); } + +void h2_io_signal_init(h2_io *io, h2_io_op op, int timeout_secs, apr_thread_cond_t *cond) +{ + io->timed_op = op; + io->timed_cond = cond; + if (timeout_secs > 0) { + io->timeout_at = apr_time_now() + apr_time_from_sec(timeout_secs); + } + else { + io->timeout_at = 0; + } +} + +void h2_io_signal_exit(h2_io *io) +{ + io->timed_cond = NULL; + io->timeout_at = 0; +} + +apr_status_t h2_io_signal_wait(h2_mplx *m, h2_io *io) +{ + apr_status_t status; + + if (io->timeout_at != 0) { + status = apr_thread_cond_timedwait(io->timed_cond, m->lock, io->timeout_at); + if (APR_STATUS_IS_TIMEUP(status)) { + ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, m->c, + "h2_mplx(%ld-%d): stream timeout expired: %s", + m->id, io->id, + (io->timed_op == H2_IO_READ)? "read" : "write"); + h2_io_rst(io, H2_ERR_CANCEL); + } + } + else { + apr_thread_cond_wait(io->timed_cond, m->lock); + status = APR_SUCCESS; + } + if (io->orphaned && status == APR_SUCCESS) { + return APR_ECONNABORTED; + } + return status; +} + +void h2_io_signal(h2_io *io, h2_io_op op) +{ + if (io->timed_cond && (io->timed_op == op || H2_IO_ANY == op)) { + apr_thread_cond_signal(io->timed_cond); + } +} + +void h2_io_make_orphaned(h2_io *io, int error) +{ + io->orphaned = 1; + if (error) { + h2_io_rst(io, error); + } + /* if someone is waiting, wake him up */ + h2_io_signal(io, H2_IO_ANY); +} + static int add_trailer(void *ctx, const char *key, const char *value) { apr_bucket_brigade *bb = ctx; diff --git a/modules/http2/h2_io.h b/modules/http2/h2_io.h index c2ecbb9122..3f0b96dbc4 100644 --- a/modules/http2/h2_io.h +++ b/modules/http2/h2_io.h @@ -18,6 +18,7 @@ struct h2_response; struct apr_thread_cond_t; +struct h2_mplx; struct h2_request; @@ -25,6 +26,12 @@ typedef apr_status_t h2_io_data_cb(void *ctx, const char *data, apr_off_t len); typedef int h2_stream_pri_cmp(int stream_id1, int stream_id2, void *ctx); +typedef enum { + H2_IO_READ, + H2_IO_WRITE, + H2_IO_ANY, +} +h2_io_op; typedef struct h2_io h2_io; @@ -39,16 +46,18 @@ struct h2_io { struct h2_response *response;/* response for submit, once created */ int rst_error; + h2_io_op timed_op; /* which operation is waited on */ + struct apr_thread_cond_t *timed_cond; /* condition to wait on */ + apr_time_t timeout_at; /* when IO wait will time out */ + int eos_in; int eos_in_written; apr_bucket_brigade *bbin; /* input data for stream */ - struct apr_thread_cond_t *input_arrived; /* block on reading */ apr_size_t input_consumed; /* how many bytes have been read */ int eos_out; apr_bucket_brigade *bbout; /* output data from stream */ apr_bucket_alloc_t *bucket_alloc; - struct apr_thread_cond_t *output_drained; /* block on writing */ int files_handles_owned; apr_bucket_brigade *tmp; /* temporary data for chunking */ @@ -88,6 +97,14 @@ int h2_io_in_has_eos_for(h2_io *io); */ int h2_io_out_has_data(h2_io *io); +void h2_io_signal(h2_io *io, h2_io_op op); +void h2_io_signal_init(h2_io *io, h2_io_op op, int timeout_secs, + struct apr_thread_cond_t *cond); +void h2_io_signal_exit(h2_io *io); +apr_status_t h2_io_signal_wait(struct h2_mplx *m, h2_io *io); + +void h2_io_make_orphaned(h2_io *io, int error); + /******************************************************************************* * Input handling of streams. ******************************************************************************/ diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c index 51c898e236..c0cdbb67a9 100644 --- a/modules/http2/h2_mplx.c +++ b/modules/http2/h2_mplx.c @@ -145,6 +145,7 @@ h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent, m->workers = workers; m->file_handles_allowed = h2_config_geti(conf, H2_CONF_SESSION_FILES); + m->stream_timeout_secs = h2_config_geti(conf, H2_CONF_STREAM_TIMEOUT_SECS); } return m; } @@ -248,10 +249,7 @@ static int io_stream_done(h2_mplx *m, h2_io *io, int rst_error) } else { /* cleanup once task is done */ - io->orphaned = 1; - if (rst_error) { - h2_io_rst(io, rst_error); - } + h2_io_make_orphaned(io, rst_error); return 1; } } @@ -283,9 +281,9 @@ apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait) ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, "h2_mplx(%ld): release_join -> destroy, (#ios=%ld)", m->id, (long)h2_io_set_size(m->stream_ios)); + apr_thread_mutex_unlock(m->lock); h2_mplx_destroy(m); /* all gone */ - /*apr_thread_mutex_unlock(m->lock);*/ } return status; } @@ -356,8 +354,9 @@ apr_status_t h2_mplx_in_read(h2_mplx *m, apr_read_type_e block, if (APR_SUCCESS == status) { h2_io *io = h2_io_set_get(m->stream_ios, stream_id); if (io && !io->orphaned) { - io->input_arrived = iowait; H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_read_pre"); + + h2_io_signal_init(io, H2_IO_READ, m->stream_timeout_secs, iowait); status = h2_io_in_read(io, bb, -1, trailers); while (APR_STATUS_IS_EAGAIN(status) && !is_aborted(m, &status) @@ -365,11 +364,13 @@ apr_status_t h2_mplx_in_read(h2_mplx *m, apr_read_type_e block, ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, m->c, "h2_mplx(%ld-%d): wait on in data (BLOCK_READ)", m->id, stream_id); - apr_thread_cond_wait(io->input_arrived, m->lock); - status = h2_io_in_read(io, bb, -1, trailers); + status = h2_io_signal_wait(m, io); + if (status == APR_SUCCESS) { + status = h2_io_in_read(io, bb, -1, trailers); + } } H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_read_post"); - io->input_arrived = NULL; + h2_io_signal_exit(io); } else { status = APR_EOF; @@ -394,9 +395,7 @@ apr_status_t h2_mplx_in_write(h2_mplx *m, int stream_id, H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_write_pre"); status = h2_io_in_write(io, bb); H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_write_post"); - if (io->input_arrived) { - apr_thread_cond_signal(io->input_arrived); - } + h2_io_signal(io, H2_IO_READ); io_process_events(m, io); } else { @@ -420,9 +419,7 @@ apr_status_t h2_mplx_in_close(h2_mplx *m, int stream_id) if (io && !io->orphaned) { status = h2_io_in_close(io); H2_MPLX_IO_IN(APLOG_TRACE2, m, io, "h2_mplx_in_close"); - if (io->input_arrived) { - apr_thread_cond_signal(io->input_arrived); - } + h2_io_signal(io, H2_IO_READ); io_process_events(m, io); } else { @@ -496,8 +493,8 @@ apr_status_t h2_mplx_out_readx(h2_mplx *m, int stream_id, status = h2_io_out_readx(io, cb, ctx, plen, peos); H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_post"); - if (status == APR_SUCCESS && cb && io->output_drained) { - apr_thread_cond_signal(io->output_drained); + if (status == APR_SUCCESS && cb) { + h2_io_signal(io, H2_IO_WRITE); } } else { @@ -529,8 +526,8 @@ apr_status_t h2_mplx_out_read_to(h2_mplx *m, int stream_id, status = h2_io_out_read_to(io, bb, plen, peos); H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_read_to_post"); - if (status == APR_SUCCESS && io->output_drained) { - apr_thread_cond_signal(io->output_drained); + if (status == APR_SUCCESS) { + h2_io_signal(io, H2_IO_WRITE); } } else { @@ -576,7 +573,7 @@ h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams) "h2_mplx(%ld): stream for response %d closed, " "resetting io to close request processing", m->id, io->id); - io->orphaned = 1; + h2_io_make_orphaned(io, H2_ERR_STREAM_CLOSED); if (io->task_done) { io_destroy(m, io, 1); } @@ -585,14 +582,11 @@ h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_stream_set *streams) * shutdown input and send out any events (e.g. window * updates) asap. */ h2_io_in_shutdown(io); - h2_io_rst(io, H2_ERR_STREAM_CLOSED); io_process_events(m, io); } } - if (io->output_drained) { - apr_thread_cond_signal(io->output_drained); - } + h2_io_signal(io, H2_IO_WRITE); } apr_thread_mutex_unlock(m->lock); } @@ -610,28 +604,29 @@ static apr_status_t out_write(h2_mplx *m, h2_io *io, * We will not split buckets to enforce the limit to the last * byte. After all, the bucket is already in memory. */ - while (!APR_BRIGADE_EMPTY(bb) - && (status == APR_SUCCESS) + while (status == APR_SUCCESS + && !APR_BRIGADE_EMPTY(bb) && !is_aborted(m, &status)) { status = h2_io_out_write(io, bb, m->stream_max_mem, trailers, &m->file_handles_allowed); - /* Wait for data to drain until there is room again */ - while (!APR_BRIGADE_EMPTY(bb) + /* Wait for data to drain until there is room again or + * stream timeout expires */ + h2_io_signal_init(io, H2_IO_WRITE, m->stream_timeout_secs, iowait); + while (status == APR_SUCCESS + && !APR_BRIGADE_EMPTY(bb) && iowait - && status == APR_SUCCESS && (m->stream_max_mem <= h2_io_out_length(io)) && !is_aborted(m, &status)) { trailers = NULL; - io->output_drained = iowait; if (f) { ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c, "h2_mplx(%ld-%d): waiting for out drain", m->id, io->id); } - apr_thread_cond_wait(io->output_drained, m->lock); - io->output_drained = NULL; + status = h2_io_signal_wait(m, io); } + h2_io_signal_exit(io); } apr_brigade_cleanup(bb); @@ -793,9 +788,7 @@ apr_status_t h2_mplx_out_rst(h2_mplx *m, int stream_id, int error) H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_rst"); have_out_data_for(m, stream_id); - if (io->output_drained) { - apr_thread_cond_signal(io->output_drained); - } + h2_io_signal(io, H2_IO_WRITE); } else { status = APR_ECONNABORTED; diff --git a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h index 4c577d0f76..0a652ce91a 100644 --- a/modules/http2/h2_mplx.h +++ b/modules/http2/h2_mplx.h @@ -77,6 +77,7 @@ struct h2_mplx { int aborted; apr_size_t stream_max_mem; + int stream_timeout_secs; apr_pool_t *spare_pool; /* spare pool, ready for next io */ struct h2_workers *workers; diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c index f4058b2236..f80ee91bec 100644 --- a/modules/http2/h2_session.c +++ b/modules/http2/h2_session.c @@ -27,6 +27,7 @@ #include "h2_bucket_eos.h" #include "h2_config.h" #include "h2_ctx.h" +#include "h2_filter.h" #include "h2_h2.h" #include "h2_mplx.h" #include "h2_push.h" @@ -66,6 +67,9 @@ static void update_window(void *ctx, int stream_id, apr_off_t bytes_read) session->id, stream_id, (long)bytes_read); } +static apr_status_t h2_session_receive(void *ctx, + const char *data, apr_size_t len, + apr_size_t *readlen); h2_stream *h2_session_open_stream(h2_session *session, int stream_id) { @@ -708,7 +712,9 @@ static h2_session *h2_session_create_int(conn_rec *c, session->max_stream_count = h2_config_geti(session->config, H2_CONF_MAX_STREAMS); session->max_stream_mem = h2_config_geti(session->config, H2_CONF_STREAM_MAX_MEM); - + session->timeout_secs = h2_config_geti(session->config, H2_CONF_TIMEOUT_SECS); + session->keepalive_secs = h2_config_geti(session->config, H2_CONF_KEEPALIVE_SECS); + status = apr_thread_cond_create(&session->iowait, session->pool); if (status != APR_SUCCESS) { return NULL; @@ -721,6 +727,11 @@ static h2_session *h2_session_create_int(conn_rec *c, h2_mplx_set_consumed_cb(session->mplx, update_window, session); + /* Install the connection input filter that feeds the session */ + session->cin = h2_filter_cin_create(session->pool, h2_session_receive, session); + h2_filter_cin_timeout_set(session->cin, session->timeout_secs); + ap_add_input_filter("H2_IN", session->cin, r, c); + h2_conn_io_init(&session->io, c, session->config, session->pool); session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc); @@ -1557,10 +1568,10 @@ static apr_status_t h2_session_send(h2_session *session) return APR_SUCCESS; } -apr_status_t h2_session_receive(h2_session *session, - const char *data, apr_size_t len, - apr_size_t *readlen) +static apr_status_t h2_session_receive(void *ctx, const char *data, + apr_size_t len, apr_size_t *readlen) { + h2_session *session = ctx; if (len > 0) { ssize_t n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len); @@ -1713,7 +1724,8 @@ apr_status_t h2_session_process(h2_session *session, int async) || idle || (!h2_stream_set_has_unsubmitted(session->streams) && !h2_stream_set_has_suspended(session->streams))); - + + h2_filter_cin_timeout_set(session->cin, idle? session->keepalive_secs : session->timeout_secs); status = h2_session_read(session, may_block && !async); ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c, diff --git a/modules/http2/h2_session.h b/modules/http2/h2_session.h index ba6243aa91..086ab18a78 100644 --- a/modules/http2/h2_session.h +++ b/modules/http2/h2_session.h @@ -41,6 +41,7 @@ struct apr_thread_mutext_t; struct apr_thread_cond_t; struct h2_ctx; struct h2_config; +struct h2_filter_cin; struct h2_mplx; struct h2_priority; struct h2_push; @@ -83,6 +84,10 @@ struct h2_session { apr_bucket_brigade *bbtmp; /* brigade for keeping temporary data */ struct apr_thread_cond_t *iowait; /* our cond when trywaiting for data */ + int timeout_secs; /* connection timeout (seconds) */ + int keepalive_secs; /* connection idle timeout (seconds) */ + + struct h2_filter_cin *cin; /* connection input filter context */ h2_conn_io io; /* io on httpd conn filters */ struct h2_mplx *mplx; /* multiplexer for stream data */ @@ -122,14 +127,6 @@ h2_session *h2_session_create(conn_rec *c, struct h2_ctx *ctx, h2_session *h2_session_rcreate(request_rec *r, struct h2_ctx *ctx, struct h2_workers *workers); -/** - * Recieve len bytes of raw HTTP/2 input data. Return the amount - * consumed and if the session is done. - */ -apr_status_t h2_session_receive(h2_session *session, - const char *data, apr_size_t len, - apr_size_t *readlen); - /** * Process the given HTTP/2 session until it is ended or a fatal * error occured. diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h index 67842e4b8e..6e338f362a 100644 --- a/modules/http2/h2_version.h +++ b/modules/http2/h2_version.h @@ -20,7 +20,7 @@ * @macro * Version number of the h2 module as c string */ -#define MOD_HTTP2_VERSION "1.0.12-DEV" +#define MOD_HTTP2_VERSION "1.0.12-DEVd" /** * @macro -- 2.40.0