You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2015/12/22 16:52:10 UTC
svn commit: r1721417 - in /httpd/httpd/trunk: ./ modules/http2/
Author: icing
Date: Tue Dec 22 15:52:10 2015
New Revision: 1721417
URL: http://svn.apache.org/viewvc?rev=1721417&view=rev
Log:
rewrote http2 connection state handling, fixed keepalive timeout handling for async and sync MPMs, cleaned logging, improved scoreboard updates of http2 master connections
Modified:
httpd/httpd/trunk/CHANGES
httpd/httpd/trunk/modules/http2/h2_config.c
httpd/httpd/trunk/modules/http2/h2_config.h
httpd/httpd/trunk/modules/http2/h2_conn.c
httpd/httpd/trunk/modules/http2/h2_conn.h
httpd/httpd/trunk/modules/http2/h2_conn_io.c
httpd/httpd/trunk/modules/http2/h2_conn_io.h
httpd/httpd/trunk/modules/http2/h2_filter.c
httpd/httpd/trunk/modules/http2/h2_filter.h
httpd/httpd/trunk/modules/http2/h2_h2.c
httpd/httpd/trunk/modules/http2/h2_io.c
httpd/httpd/trunk/modules/http2/h2_session.c
httpd/httpd/trunk/modules/http2/h2_session.h
httpd/httpd/trunk/modules/http2/h2_util.c
httpd/httpd/trunk/modules/http2/h2_workers.c
Modified: httpd/httpd/trunk/CHANGES
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/CHANGES?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/CHANGES [utf-8] (original)
+++ httpd/httpd/trunk/CHANGES [utf-8] Tue Dec 22 15:52:10 2015
@@ -2,7 +2,9 @@
Changes with Apache 2.5.0
*) mod_http2: On async MPMs (event), idle HTTP/2 connections will enter KEEPALIVE
- state and become eligible for MPM cleanup under load.
+ state and become eligible for MPM cleanup under load. Rewrote connection
+ state handling. Defaults for H2KeepAliveTimeout shortened. Cleaned up logging.
+ Improved scoreboard updates for http2 master threads.
[Stefan Eissing]
*) mod_http2: new r->subprocess_env variables HTTP2 and H2PUSH, set to "on"
Modified: httpd/httpd/trunk/modules/http2/h2_config.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_config.c?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_config.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_config.c Tue Dec 22 15:52:10 2015
@@ -60,12 +60,11 @@ static h2_config defconf = {
1, /* HTTP/2 server push enabled */
NULL, /* map of content-type to priorities */
5, /* normal connection timeout */
- 30, /* idle connection timeout */
+ 5, /* keepalive timeout */
0, /* stream timeout */
};
static int files_per_session;
-static int async_mpm;
void h2_config_init(apr_pool_t *pool)
{
@@ -90,14 +89,6 @@ void h2_config_init(apr_pool_t *pool)
/* don't know anything about it, stay safe */
break;
}
- if (ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm) != APR_SUCCESS) {
- async_mpm = 0;
- }
-}
-
-int h2_config_async_mpm(void)
-{
- return async_mpm;
}
static void *h2_config_create(apr_pool_t *pool,
Modified: httpd/httpd/trunk/modules/http2/h2_config.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_config.h?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_config.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_config.h Tue Dec 22 15:52:10 2015
@@ -95,7 +95,5 @@ void h2_config_init(apr_pool_t *pool);
const struct h2_priority *h2_config_get_priority(const h2_config *conf,
const char *content_type);
-int h2_config_async_mpm(void);
-
#endif /* __mod_h2__h2_config_h__ */
Modified: httpd/httpd/trunk/modules/http2/h2_conn.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_conn.c?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_conn.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_conn.c Tue Dec 22 15:52:10 2015
@@ -43,6 +43,7 @@ static struct h2_workers *workers;
static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN;
static module *mpm_module;
+static int async_mpm;
static void check_modules(int force)
{
@@ -77,17 +78,24 @@ apr_status_t h2_conn_child_init(apr_pool
{
const h2_config *config = h2_config_sget(s);
apr_status_t status = APR_SUCCESS;
- int minw = h2_config_geti(config, H2_CONF_MIN_WORKERS);
- int maxw = h2_config_geti(config, H2_CONF_MAX_WORKERS);
+ int minw, maxw;
int max_threads_per_child = 0;
int idle_secs = 0;
- h2_config_init(pool);
+ check_modules(1);
ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads_per_child);
- check_modules(1);
+ status = ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm);
+ if (status != APR_SUCCESS) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, status, s, "querying MPM for async");
+ async_mpm = 0;
+ }
+
+ h2_config_init(pool);
+ minw = h2_config_geti(config, H2_CONF_MIN_WORKERS);
+ maxw = h2_config_geti(config, H2_CONF_MAX_WORKERS);
if (minw <= 0) {
minw = max_threads_per_child;
}
@@ -145,9 +153,8 @@ apr_status_t h2_conn_setup(h2_ctx *ctx,
return APR_SUCCESS;
}
-apr_status_t h2_conn_process(h2_ctx *ctx, int async)
+static apr_status_t h2_conn_process(h2_ctx *ctx)
{
- apr_status_t status;
h2_session *session;
session = h2_ctx_session_get(ctx);
@@ -155,15 +162,15 @@ apr_status_t h2_conn_process(h2_ctx *ctx
session->c->cs->sense = CONN_SENSE_DEFAULT;
}
- status = h2_session_process(session, async);
+ h2_session_process(session, async_mpm);
session->c->keepalive = AP_CONN_KEEPALIVE;
if (session->c->cs) {
session->c->cs->state = CONN_STATE_WRITE_COMPLETION;
}
- if (status == APR_EOF) {
- ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
+ if (session->state == H2_SESSION_ST_CLOSING) {
+ ap_log_cerror( APLOG_MARK, APLOG_DEBUG, 0, session->c,
"h2_session(%ld): done", session->id);
/* Make sure this connection gets closed properly. */
ap_update_child_status_from_conn(session->c->sbh, SERVER_CLOSING, session->c);
@@ -178,9 +185,16 @@ apr_status_t h2_conn_process(h2_ctx *ctx
apr_status_t h2_conn_run(struct h2_ctx *ctx, conn_rec *c)
{
+ int mpm_state = 0;
do {
- h2_conn_process(ctx, 0);
- } while (c->keepalive == AP_CONN_KEEPALIVE && !c->aborted);
+ h2_conn_process(ctx);
+
+ if (ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
+ break;
+ }
+ } while (!async_mpm
+ && c->keepalive == AP_CONN_KEEPALIVE
+ && mpm_state != AP_MPMQ_STOPPING);
return DONE;
}
Modified: httpd/httpd/trunk/modules/http2/h2_conn.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_conn.h?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_conn.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_conn.h Tue Dec 22 15:52:10 2015
@@ -29,16 +29,6 @@ struct h2_task;
apr_status_t h2_conn_setup(struct h2_ctx *ctx, conn_rec *c, request_rec *r);
/**
- * Process the HTTP/2 connection. Return whenever blocking reads or
- * long writes have to be performed.
- *
- * @param ctx the http2 context to process
- * @return APR_SUCCESS as long as processing needs to continue, APR_EOF
- * when HTTP/2 session is done.
- */
-apr_status_t h2_conn_process(struct h2_ctx *ctx, int async);
-
-/**
* Run the HTTP/2 connection in synchronous fashion.
* Return when the HTTP/2 session is done
* and the connection will close or a fatal error occured.
Modified: httpd/httpd/trunk/modules/http2/h2_conn_io.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_conn_io.c?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_conn_io.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_conn_io.c Tue Dec 22 15:52:10 2015
@@ -79,7 +79,7 @@ apr_status_t h2_conn_io_init(h2_conn_io
}
if (APLOGctrace1(c)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->connection,
"h2_conn_io(%ld): init, buffering=%d, warmup_size=%ld, cd_secs=%f",
io->connection->id, io->buffer_output, (long)io->warmup_size,
((float)io->cooldown_usecs/APR_USEC_PER_SEC));
@@ -106,7 +106,7 @@ static apr_status_t pass_out(apr_bucket_
ap_update_child_status(io->connection->sbh, SERVER_BUSY_WRITE, NULL);
status = apr_brigade_length(bb, 0, &bblen);
if (status == APR_SUCCESS) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->connection,
"h2_conn_io(%ld): pass_out brigade %ld bytes",
io->connection->id, (long)bblen);
status = ap_pass_brigade(io->connection->output_filters, bb);
@@ -135,7 +135,7 @@ static apr_status_t bucketeer_buffer(h2_
/* long time not written, reset write size */
io->write_size = WRITE_SIZE_INITIAL;
io->bytes_written = 0;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->connection,
"h2_conn_io(%ld): timeout write size reset to %ld",
(long)io->connection->id, (long)io->write_size);
}
@@ -143,7 +143,7 @@ static apr_status_t bucketeer_buffer(h2_
&& io->bytes_written >= io->warmup_size) {
/* connection is hot, use max size */
io->write_size = WRITE_SIZE_MAX;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->connection,
"h2_conn_io(%ld): threshold reached, write size now %ld",
(long)io->connection->id, (long)io->write_size);
}
@@ -172,7 +172,7 @@ apr_status_t h2_conn_io_write(h2_conn_io
io->unflushed = 1;
if (io->bufsize > 0) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->connection,
"h2_conn_io: buffering %ld bytes", (long)length);
if (!APR_BRIGADE_EMPTY(io->output)) {
@@ -203,7 +203,7 @@ apr_status_t h2_conn_io_write(h2_conn_io
}
else {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, io->connection,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, status, io->connection,
"h2_conn_io: writing %ld bytes to brigade", (long)length);
status = apr_brigade_write(io->output, pass_out, io, buf, length);
}
@@ -247,7 +247,7 @@ static apr_status_t h2_conn_io_flush_int
if (io->unflushed || force) {
if (io->buflen > 0) {
/* something in the buffer, put it in the output brigade */
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->connection,
"h2_conn_io: flush, flushing %ld bytes", (long)io->buflen);
bucketeer_buffer(io);
io->buflen = 0;
@@ -258,7 +258,7 @@ static apr_status_t h2_conn_io_flush_int
apr_bucket_flush_create(io->output->bucket_alloc));
}
- ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection,
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->connection,
"h2_conn_io: flush");
/* Send it out */
io->unflushed = 0;
@@ -279,17 +279,3 @@ apr_status_t h2_conn_io_pass(h2_conn_io
return h2_conn_io_flush_int(io, 0);
}
-apr_status_t h2_conn_io_close(h2_conn_io *io, void *session)
-{
- apr_bucket *b;
-
- /* Send out anything in our buffers */
- h2_conn_io_flush_int(io, 0);
-
- b = h2_bucket_eoc_create(io->connection->bucket_alloc, session);
- APR_BRIGADE_INSERT_TAIL(io->output, b);
- b = apr_bucket_flush_create(io->connection->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(io->output, b);
- return ap_pass_brigade(io->connection->output_filters, io->output);
- /* and all is gone */
-}
\ No newline at end of file
Modified: httpd/httpd/trunk/modules/http2/h2_conn_io.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_conn_io.h?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_conn_io.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_conn_io.h Tue Dec 22 15:52:10 2015
@@ -60,6 +60,5 @@ apr_status_t h2_conn_io_consider_flush(h
apr_status_t h2_conn_io_pass(h2_conn_io *io);
apr_status_t h2_conn_io_flush(h2_conn_io *io);
-apr_status_t h2_conn_io_close(h2_conn_io *io, void *session);
#endif /* defined(__mod_h2__h2_conn_io__) */
Modified: httpd/httpd/trunk/modules/http2/h2_filter.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_filter.c?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_filter.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_filter.c Tue Dec 22 15:52:10 2015
@@ -58,7 +58,7 @@ static apr_status_t consume_brigade(h2_f
status = apr_bucket_split(bucket, consumed);
}
readlen += consumed;
- cin->last_read = apr_time_now();
+ cin->start_read = apr_time_now();
}
}
apr_bucket_delete(bucket);
@@ -70,22 +70,6 @@ static apr_status_t consume_brigade(h2_f
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;
@@ -94,7 +78,7 @@ h2_filter_cin *h2_filter_cin_create(apr_
cin->pool = p;
cin->cb = cb;
cin->cb_ctx = ctx;
- cin->last_read = UNSET;
+ cin->start_read = UNSET;
return cin;
}
@@ -112,11 +96,11 @@ apr_status_t h2_filter_core_input(ap_fil
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_TRACE1, 0, f->c,
- "core_input: read, block=%d, mode=%d, readbytes=%ld",
- block, mode, (long)readbytes);
+ "core_input(%ld): read, %s, mode=%d, readbytes=%ld, timeout=%d",
+ (long)f->c->id, (block == APR_BLOCK_READ)? "BLOCK_READ" : "NONBLOCK_READ",
+ mode, (long)readbytes, cin->timeout_secs);
if (mode == AP_MODE_INIT || mode == AP_MODE_SPECULATIVE) {
return ap_get_brigade(f->next, brigade, mode, block, readbytes);
@@ -132,9 +116,9 @@ apr_status_t h2_filter_core_input(ap_fil
if (!cin->socket) {
cin->socket = ap_get_conn_socket(f->c);
- cin->last_read = apr_time_now(); /* first call */
}
+ cin->start_read = apr_time_now();
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.
@@ -142,40 +126,30 @@ apr_status_t h2_filter_core_input(ap_fil
* 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);
+ if (cin->timeout_secs > 0) {
+ apr_time_t t = apr_time_from_sec(cin->timeout_secs);
+ apr_socket_timeout_get(cin->socket, &saved_timeout);
+ apr_socket_timeout_set(cin->socket, H2MIN(t, saved_timeout));
}
}
- else {
- ap_update_child_status(f->c->sbh, SERVER_BUSY_READ, NULL);
- }
-
+ ap_update_child_status_from_conn(f->c->sbh, SERVER_BUSY_READ, f->c);
status = ap_get_brigade(f->next, cin->bb, AP_MODE_READBYTES,
block, readbytes);
+ if (saved_timeout != UNSET) {
+ apr_socket_timeout_set(cin->socket, saved_timeout);
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+ "core_input(%ld): got_brigade", (long)f->c->id);
}
-out:
switch (status) {
case APR_SUCCESS:
- 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);
- }
+ break;
case APR_EOF:
case APR_EAGAIN:
+ case APR_TIMEUP:
break;
default:
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, f->c,
Modified: httpd/httpd/trunk/modules/http2/h2_filter.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_filter.h?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_filter.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_filter.h Tue Dec 22 15:52:10 2015
@@ -29,7 +29,7 @@ typedef struct h2_filter_cin {
void *cb_ctx;
apr_socket_t *socket;
int timeout_secs;
- apr_time_t last_read;
+ apr_time_t start_read;
} h2_filter_cin;
h2_filter_cin *h2_filter_cin_create(apr_pool_t *p, h2_filter_cin_cb *cb, void *ctx);
Modified: httpd/httpd/trunk/modules/http2/h2_h2.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_h2.c?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_h2.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_h2.c Tue Dec 22 15:52:10 2015
@@ -651,12 +651,7 @@ int h2_h2_process_conn(conn_rec* c)
return status;
}
}
- if (h2_config_async_mpm()) {
- return h2_conn_process(ctx, 1);
- }
- else {
- return h2_conn_run(ctx, c);
- }
+ return h2_conn_run(ctx, c);
}
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, "h2_h2, declined");
Modified: httpd/httpd/trunk/modules/http2/h2_io.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_io.c?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_io.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_io.c Tue Dec 22 15:52:10 2015
@@ -221,11 +221,6 @@ apr_status_t h2_io_in_read(h2_io *io, ap
if (io->request->chunked) {
/* the reader expects HTTP/1.1 chunked encoding */
- if (!io->tmp) {
- io->tmp = apr_brigade_create(io->pool, io->bucket_alloc);
- }
- apr_brigade_cleanup(io->tmp);
-
status = h2_util_move(io->tmp, io->bbin, maxlen, NULL, "h2_io_in_read_chunk");
if (status == APR_SUCCESS) {
apr_off_t tmp_len = 0;
@@ -245,6 +240,7 @@ apr_status_t h2_io_in_read(h2_io *io, ap
else {
status = h2_util_move(bb, io->tmp, -1, NULL, "h2_io_in_read_tmp2");
}
+ apr_brigade_cleanup(io->tmp);
}
}
else {
@@ -274,6 +270,7 @@ apr_status_t h2_io_in_write(h2_io *io, a
if (!APR_BRIGADE_EMPTY(bb)) {
if (!io->bbin) {
io->bbin = apr_brigade_create(io->pool, io->bucket_alloc);
+ io->tmp = apr_brigade_create(io->pool, io->bucket_alloc);
}
return h2_util_move(io->bbin, bb, -1, NULL, "h2_io_in_write");
}
Modified: httpd/httpd/trunk/modules/http2/h2_session.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_session.c?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_session.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_session.c Tue Dec 22 15:52:10 2015
@@ -22,8 +22,10 @@
#include <http_core.h>
#include <http_config.h>
#include <http_log.h>
+#include <scoreboard.h>
#include "h2_private.h"
+#include "h2_bucket_eoc.h"
#include "h2_bucket_eos.h"
#include "h2_config.h"
#include "h2_ctx.h"
@@ -42,6 +44,9 @@
#include "h2_version.h"
#include "h2_workers.h"
+#define H2MAX(x,y) ((x) > (y) ? (x) : (y))
+#define H2MIN(x,y) ((x) < (y) ? (x) : (y))
+
static int frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen);
static int h2_session_status_from_apr_status(apr_status_t rv)
@@ -454,6 +459,12 @@ static int on_frame_recv_cb(nghttp2_sess
(int)frame->rst_stream.error_code);
++session->streams_reset;
break;
+ case NGHTTP2_GOAWAY:
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "h2_session(%ld): GOAWAY errror=%d",
+ session->id, (int)frame->goaway.error_code);
+ session->client_goaway = 1;
+ break;
default:
if (APLOGctrace2(session->c)) {
char buffer[256];
@@ -639,6 +650,52 @@ static apr_status_t init_callbacks(conn_
return APR_SUCCESS;
}
+static void h2_session_cleanup(h2_session *session)
+{
+ AP_DEBUG_ASSERT(session);
+ /* This is an early cleanup of the session that may
+ * discard what is no longer necessary for *new* streams
+ * and general HTTP/2 processing.
+ * At this point, all frames are in transit or somehwere in
+ * our buffers or passed down output filters.
+ * h2 streams might still being written out.
+ */
+ if (session->c) {
+ h2_ctx_clear(session->c);
+ }
+ if (session->ngh2) {
+ nghttp2_session_del(session->ngh2);
+ session->ngh2 = NULL;
+ }
+ if (session->spare) {
+ apr_pool_destroy(session->spare);
+ session->spare = NULL;
+ }
+}
+
+static void h2_session_destroy(h2_session *session)
+{
+ AP_DEBUG_ASSERT(session);
+ h2_session_cleanup(session);
+
+ if (APLOGctrace1(session->c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+ "h2_session(%ld): destroy, %d streams open",
+ session->id, (int)h2_stream_set_size(session->streams));
+ }
+ if (session->mplx) {
+ h2_mplx_release_and_join(session->mplx, session->iowait);
+ session->mplx = NULL;
+ }
+ if (session->streams) {
+ h2_stream_set_destroy(session->streams);
+ session->streams = NULL;
+ }
+ if (session->pool) {
+ apr_pool_destroy(session->pool);
+ }
+}
+
static apr_status_t session_pool_cleanup(void *data)
{
h2_session *session = data;
@@ -721,6 +778,8 @@ static h2_session *h2_session_create_int
session->s = h2_ctx_server_get(ctx);
session->config = h2_config_sget(session->s);
+ session->state = H2_SESSION_ST_INIT;
+
session->pool = pool;
apr_pool_pre_cleanup_register(pool, session, session_pool_cleanup);
@@ -743,7 +802,6 @@ static h2_session *h2_session_create_int
/* 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);
@@ -811,53 +869,6 @@ h2_session *h2_session_rcreate(request_r
return h2_session_create_int(r->connection, r, ctx, workers);
}
-static void h2_session_cleanup(h2_session *session)
-{
- AP_DEBUG_ASSERT(session);
- /* This is an early cleanup of the session that may
- * discard what is no longer necessary for *new* streams
- * and general HTTP/2 processing.
- * At this point, all frames are in transit or somehwere in
- * our buffers or passed down output filters.
- * h2 streams might still being written out.
- */
- if (session->c) {
- h2_ctx_clear(session->c);
- }
- if (session->ngh2) {
- nghttp2_session_del(session->ngh2);
- session->ngh2 = NULL;
- }
- if (session->spare) {
- apr_pool_destroy(session->spare);
- session->spare = NULL;
- }
-}
-
-void h2_session_destroy(h2_session *session)
-{
- AP_DEBUG_ASSERT(session);
- h2_session_cleanup(session);
-
- if (APLOGctrace1(session->c)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
- "h2_session(%ld): destroy, %d streams open",
- session->id, (int)h2_stream_set_size(session->streams));
- }
- if (session->mplx) {
- h2_mplx_release_and_join(session->mplx, session->iowait);
- session->mplx = NULL;
- }
- if (session->streams) {
- h2_stream_set_destroy(session->streams);
- session->streams = NULL;
- }
- if (session->pool) {
- apr_pool_destroy(session->pool);
- }
-}
-
-
void h2_session_eoc_callback(h2_session *session)
{
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
@@ -869,36 +880,33 @@ void h2_session_eoc_callback(h2_session
static apr_status_t h2_session_abort_int(h2_session *session, int reason)
{
AP_DEBUG_ASSERT(session);
- if (!session->aborted) {
- session->aborted = 1;
-
- if (session->ngh2) {
- if (NGHTTP2_ERR_EOF == reason) {
- /* This is our way of indication that the connection is
- * gone. No use to send any GOAWAY frames. */
- nghttp2_session_terminate_session(session->ngh2, reason);
- }
- else if (!reason) {
- nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE,
- session->max_stream_received,
- reason, NULL, 0);
- nghttp2_session_send(session->ngh2);
- }
- else {
- const char *err = nghttp2_strerror(reason);
-
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
- "session(%ld): abort, reason=%d %s",
- session->id, reason, err);
-
- /* The connection might still be there and we shut down
- * with GOAWAY and reason information. */
- nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE,
- session->max_stream_received,
- reason, (const uint8_t *)err,
- strlen(err));
- nghttp2_session_send(session->ngh2);
- }
+ session->aborted = 1;
+ if (session->state != H2_SESSION_ST_CLOSING) {
+ session->state = H2_SESSION_ST_CLOSING;
+ if (session->client_goaway) {
+ /* client sent us a GOAWAY, just terminate */
+ nghttp2_session_terminate_session(session->ngh2, NGHTTP2_ERR_EOF);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "session(%ld): closed, GOAWAY from client", session->id);
+ }
+ else if (!reason) {
+ nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE,
+ session->max_stream_received,
+ reason, NULL, 0);
+ nghttp2_session_send(session->ngh2);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "session(%ld): closed, no err", session->id);
+ }
+ else {
+ const char *err = nghttp2_strerror(reason);
+ nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE,
+ session->max_stream_received,
+ reason, (const uint8_t *)err,
+ strlen(err));
+ nghttp2_session_send(session->ngh2);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+ "session(%ld): closed, err=%d '%s'",
+ session->id, reason, err);
}
h2_mplx_abort(session->mplx);
}
@@ -1092,14 +1100,20 @@ h2_stream *h2_session_get_stream(h2_sess
return session->last_stream;
}
-apr_status_t h2_session_close(h2_session *session)
+void h2_session_close(h2_session *session)
{
+ apr_bucket *b;
+
AP_DEBUG_ASSERT(session);
if (!session->aborted) {
h2_session_abort_int(session, 0);
}
h2_session_cleanup(session);
- return h2_conn_io_close(&session->io, session);
+
+ b = h2_bucket_eoc_create(session->c->bucket_alloc, session);
+ h2_conn_io_writeb(&session->io, b);
+ h2_conn_io_flush(&session->io);
+ /* and all is or will be destroyed */
}
static ssize_t stream_data_cb(nghttp2_session *ng2s,
@@ -1574,7 +1588,6 @@ static apr_status_t h2_session_send(h2_s
}
}
- session->wait_micros = 0;
session->unsent_promises = 0;
session->unsent_submits = 0;
@@ -1594,7 +1607,7 @@ static apr_status_t h2_session_receive(v
"h2_session: nghttp2_session_mem_recv error=%d",
(int)n);
if (nghttp2_is_fatal((int)n)) {
- h2_session_abort(session, 0, (int)n);
+ h2_session_abort_int(session, (int)n);
return APR_EGENERAL;
}
}
@@ -1608,12 +1621,13 @@ static apr_status_t h2_session_receive(v
static apr_status_t h2_session_read(h2_session *session, int block, int loops)
{
apr_status_t status, rstatus = APR_EAGAIN;
+ conn_rec *c = session->c;
int i;
for (i = 0; i < loops; ++i) {
/* H2_IN filter handles all incoming data against the session.
* We just pull at the filter chain to make it happen */
- status = ap_get_brigade(session->c->input_filters,
+ status = ap_get_brigade(c->input_filters,
session->bbtmp, AP_MODE_READBYTES,
block? APR_BLOCK_READ : APR_NONBLOCK_READ,
APR_BUCKET_BUFF_SIZE);
@@ -1623,7 +1637,6 @@ static apr_status_t h2_session_read(h2_s
switch (status) {
case APR_SUCCESS:
/* successful read, reset our idle timers */
- session->wait_micros = 0;
rstatus = APR_SUCCESS;
if (block) {
/* successfull blocked read, try unblocked to
@@ -1633,6 +1646,8 @@ static apr_status_t h2_session_read(h2_s
break;
case APR_EAGAIN:
return rstatus;
+ case APR_TIMEUP:
+ return status;
default:
if (!i) {
/* first attempt failed */
@@ -1640,12 +1655,10 @@ static apr_status_t h2_session_read(h2_s
|| APR_STATUS_IS_ECONNABORTED(status)
|| APR_STATUS_IS_ECONNRESET(status)
|| APR_STATUS_IS_EOF(status)
- || APR_STATUS_IS_TIMEUP(status)
|| APR_STATUS_IS_EBADF(status)) {
/* common status for a client that has left */
- ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c,
- "h2_session(%ld): terminating",
- session->id);
+ ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): input gone", session->id);
/* Stolen from mod_reqtimeout to speed up lingering when
* a read timeout happened.
*/
@@ -1653,7 +1666,7 @@ static apr_status_t h2_session_read(h2_s
}
else {
/* uncommon status, log on INFO so that we see this */
- ap_log_cerror( APLOG_MARK, APLOG_INFO, status, session->c,
+ ap_log_cerror( APLOG_MARK, APLOG_INFO, status, c,
APLOGNO(02950)
"h2_session(%ld): error reading, terminating",
session->id);
@@ -1704,203 +1717,273 @@ static const int MAX_WAIT_MICROS = 200 *
apr_status_t h2_session_process(h2_session *session, int async)
{
apr_status_t status = APR_SUCCESS;
- int got_streams = 0;
- int have_written, have_read;
- int timeout_secs = session->timeout_secs;
- int keep_alive = 0;
-
- ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c,
- "h2_session(%ld): process", session->id);
+ conn_rec *c = session->c;
+ int rv, have_written, have_read, remain_secs;
+ const char *reason = "";
+ ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): process start, async=%d", session->id, async);
+
while (1) {
- have_read = 0;
- have_written = 0;
-
- if (session->aborted || (!nghttp2_session_want_read(session->ngh2)
- && !nghttp2_session_want_write(session->ngh2))) {
- ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c,
- "h2_session(%ld): process -> aborted", session->id);
- h2_conn_io_flush(&session->io);
- return APR_EOF;
- }
+ have_read = have_written = 0;
- if (!session->started) {
- int rv;
-
- if (!h2_is_acceptable_connection(session->c, 1)) {
- nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0,
- NGHTTP2_INADEQUATE_SECURITY, NULL, 0);
- }
-
- status = h2_session_start(session, &rv);
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
- "h2_session(%ld): started on %s:%d", session->id,
- session->s->server_hostname,
- session->c->local_addr->port);
- if (status != APR_SUCCESS) {
- h2_session_abort(session, status, rv);
- h2_session_eoc_callback(session);
- }
- session->started = 1;
+ if (session->aborted) {
+ reason = "aborted";
+ status = APR_EOF;
+ goto out;
}
- got_streams = !h2_stream_set_is_empty(session->streams);
-
- /* If we want client data, see if some is there. */
- if (nghttp2_session_want_read(session->ngh2)) {
- int idle = (session->responses_sent && !got_streams);
- int may_block = ((session->frames_received <= 1)
- || idle
- || (!h2_stream_set_has_unsubmitted(session->streams)
- && !h2_stream_set_has_suspended(session->streams)));
-
- h2_filter_cin_timeout_set(session->cin, timeout_secs);
- status = h2_session_read(session, may_block, 10);
-
- got_streams = !h2_stream_set_is_empty(session->streams);
- ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c,
- "h2_session(%ld): process -> read", session->id);
- if (status == APR_SUCCESS) {
- have_read = 1;
- keep_alive = 0;
- timeout_secs = session->timeout_secs;
- if (session->reprioritize) {
- ap_log_cerror( APLOG_MARK, APLOG_TRACE2, status, session->c,
- "h2_session(%ld): process -> reprioritize", session->id);
- h2_mplx_reprioritize(session->mplx, stream_pri_cmp, session);
- session->reprioritize = 0;
+ switch (session->state) {
+ case H2_SESSION_ST_INIT:
+ if (!h2_is_acceptable_connection(c, 1)) {
+ nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 0,
+ NGHTTP2_INADEQUATE_SECURITY, NULL, 0);
+ }
+
+ status = h2_session_start(session, &rv);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
+ "h2_session(%ld): started on %s:%d", session->id,
+ session->s->server_hostname,
+ c->local_addr->port);
+ if (status != APR_SUCCESS) {
+ h2_session_abort(session, status, rv);
+ h2_session_eoc_callback(session);
+ reason = "start failed";
+ goto out;
}
- }
- else if (status == APR_EAGAIN) {
- /* nothing to read */
- }
- else if (APR_STATUS_IS_TIMEUP(status)) {
- if (may_block) {
- /* I think can only happen in mayblock... */
- if (async) {
- /* timeout reading, return to async MPM with our wish to
- * read and let it handle that. It may just close the
- * connection as it considers it in KEEPALIVE mode. */
- ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c,
- "h2_session(%ld): 1st timeout, return to async "
- "MPM for KEEPALIVE, frames_received=%d, "
- "got_streams=%d, have_written=%d",
- session->id, (int)session->frames_received,
- (int)got_streams, (int)have_written);
- if (session->c->cs) {
- session->c->cs->sense = CONN_SENSE_WANT_READ;
- }
- return APR_SUCCESS;
- }
- else if (!keep_alive
- && session->keepalive_secs > timeout_secs) {
- keep_alive = 1;
- timeout_secs = session->keepalive_secs - timeout_secs;
- ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c,
- "h2_session(%ld): 1st timeout, set "
- "keepalive timeout of + %d seconds",
- session->id, (int)timeout_secs);
- status = APR_EAGAIN;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): INIT -> BUSY", session->id);
+ session->state = H2_SESSION_ST_BUSY;
+ break;
+
+ case H2_SESSION_ST_IDLE_READ:
+ h2_filter_cin_timeout_set(session->cin, session->timeout_secs);
+ ap_update_child_status(c->sbh, SERVER_BUSY_READ, NULL);
+ status = h2_session_read(session, 1, 10);
+ if (APR_STATUS_IS_TIMEUP(status)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): IDLE -> KEEPALIVE", session->id);
+ session->state = H2_SESSION_ST_KEEPALIVE;
+ }
+ else if (status == APR_SUCCESS) {
+ /* got something, go busy again */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): IDLE -> BUSY", session->id);
+ session->state = H2_SESSION_ST_BUSY;
+ }
+ else {
+ reason = "keepalive error";
+ goto out;
+ }
+ break;
+
+ case H2_SESSION_ST_BUSY:
+ if (nghttp2_session_want_read(session->ngh2)) {
+ status = h2_session_read(session, 0, 10);
+ if (status == APR_SUCCESS) {
+ /* got something, continue processing */
+ have_read = 1;
+ }
+ else if (status == APR_EAGAIN) {
+ /* nothing to read */
}
else {
- /* fall thriough */
- ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c,
- "h2_session(%ld): keepalive expired, close",
- session->id);
+ reason = "busy read error";
+ goto out;
}
}
- ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c,
- "h2_session(%ld): timeout", session->id);
- return status;
- }
- else {
- ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
- "h2_session(%ld): failed read", session->id);
- return status;
- }
- }
-
- if (got_streams) {
- ap_log_cerror( APLOG_MARK, APLOG_TRACE2, status, session->c,
- "h2_session(%ld): process -> check resume", session->id);
- /* resume any streams for which data is available again */
- h2_session_resume_streams_with_data(session);
-
- /* Submit any responses/push_promises that are ready */
- ap_log_cerror( APLOG_MARK, APLOG_TRACE2, status, session->c,
- "h2_session(%ld): process -> check submit", session->id);
- status = h2_session_submit(session);
- if (status == APR_SUCCESS) {
- have_written = 1;
- }
- else if (status != APR_EAGAIN) {
- return status;
- }
-
- /* Check that any pending window updates are sent. */
- ap_log_cerror( APLOG_MARK, APLOG_TRACE2, status, session->c,
- "h2_session(%ld): process -> check window_update", session->id);
- status = h2_mplx_in_update_windows(session->mplx);
- if (status == APR_SUCCESS) {
- /* need to flush window updates onto the connection asap */
+
+ if (!h2_stream_set_is_empty(session->streams)) {
+ /* resume any streams for which data is available again */
+ h2_session_resume_streams_with_data(session);
+ /* Submit any responses/push_promises that are ready */
+ status = h2_session_submit(session);
+ if (status == APR_SUCCESS) {
+ have_written = 1;
+ }
+ else if (status != APR_EAGAIN) {
+ reason = "submit error";
+ goto out;
+ }
+ /* send out window updates for our inputs */
+ status = h2_mplx_in_update_windows(session->mplx);
+ if (status != APR_SUCCESS && status != APR_EAGAIN) {
+ reason = "window update error";
+ goto out;
+ }
+ }
+
+ if (nghttp2_session_want_write(session->ngh2)) {
+ status = h2_session_send(session);
+ if (status != APR_SUCCESS) {
+ reason = "send error";
+ goto out;
+ }
+ have_written = 1;
+ }
+
+ if (have_read || have_written) {
+ session->wait_us = 0;
+ }
+ else {
+ /* nothing for input and output to do. If we remain
+ * in this state, we go into a tight loop and suck up
+ * CPU cycles.
+ * Ideally, we'd like to do a blocking read, but that
+ * is not possible if we have scheduled tasks and wait
+ * for them to produce something. */
+ if (h2_stream_set_is_empty(session->streams)) {
+ /* When we have no streams, no task event are possible,
+ * switch to blocking reads */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): BUSY -> IDLE", session->id);
+ session->state = H2_SESSION_ST_IDLE_READ;
+ }
+ else if (!h2_stream_set_has_unsubmitted(session->streams)
+ && !h2_stream_set_has_suspended(session->streams)) {
+ /* none of our streams is waiting for a response or
+ * new output data from task processing,
+ * switch to blocking reads. */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): BUSY -> IDLE", session->id);
+ session->state = H2_SESSION_ST_IDLE_READ;
+ }
+ else {
+ /* Unable to do blocking reads, as we wait on events from
+ * task processing in other threads. Do a busy wait with
+ * backoff timer. */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): BUSY -> WAIT", session->id);
+ session->state = H2_SESSION_ST_BUSY_WAIT;
+ }
+ }
+ break;
+
+ case H2_SESSION_ST_BUSY_WAIT:
+ session->wait_us = H2MAX(session->wait_us, 10);
+ if (APLOGctrace1(c)) {
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+ "h2_session: wait for data, %ld micros",
+ (long)session->wait_us);
+ }
+
h2_conn_io_flush(&session->io);
- }
- else if (status != APR_EAGAIN) {
- return status;
- }
- }
-
- /* Send data out first, as long as we have some.
- * We are a server after all. */
- if (nghttp2_session_want_write(session->ngh2)) {
- status = h2_session_send(session);
- ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c,
- "h2_session(%ld): process -> send", session->id);
- have_written = 1;
- if (status != APR_SUCCESS) {
- ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, session->c,
- "h2_session(%ld): failed send", session->id);
- return status;
- }
- }
-
- if (!have_read && !have_written) {
- if (session->wait_micros == 0) {
- session->wait_micros = 10;
- }
- }
-
- if (session->wait_micros > 0 && !session->aborted) {
- /* Only happens when reading returned EAGAIN and we also
- * had nothing to write.
- * This is a normal state of affairs when streams have been
- * opened, the client waiting on responses, but our workers
- * have not produced anything to send yet.
- */
- if (APLOGcdebug(session->c)) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
- "h2_session: wait for data, %ld micros",
- (long)session->wait_micros);
- }
-
- h2_conn_io_flush(&session->io);
- ap_log_cerror( APLOG_MARK, APLOG_TRACE2, status, session->c,
- "h2_session(%ld): process -> trywait", session->id);
- status = h2_mplx_out_trywait(session->mplx, session->wait_micros,
- session->iowait);
- if (status == APR_TIMEUP) {
- if (session->wait_micros < MAX_WAIT_MICROS) {
- session->wait_micros *= 2;
+ ap_log_cerror( APLOG_MARK, APLOG_TRACE2, status, c,
+ "h2_session(%ld): process -> trywait", session->id);
+ status = h2_mplx_out_trywait(session->mplx, session->wait_us,
+ session->iowait);
+ if (status == APR_SUCCESS) {
+ /* got something, go busy again */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): WAIT -> BUSY", session->id);
+ session->state = H2_SESSION_ST_BUSY;
}
- }
+ else if (status == APR_TIMEUP) {
+ if (nghttp2_session_want_read(session->ngh2)) {
+ status = h2_session_read(session, 0, 1);
+ if (status == APR_SUCCESS) {
+ /* got something, go busy again */
+ session->wait_us = 0;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): WAIT -> BUSY", session->id);
+ session->state = H2_SESSION_ST_BUSY;
+ }
+ else if (status != APR_EAGAIN) {
+ reason = "busy read error";
+ goto out;
+ }
+ }
+ /* nothing, increase timer for graceful backup */
+ session->wait_us = H2MIN(session->wait_us*2, MAX_WAIT_MICROS);
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): WAIT -> BUSY", session->id);
+ session->state = H2_SESSION_ST_BUSY;
+ }
+ else {
+ reason = "busy wait error";
+ goto out;
+ }
+ break;
+
+ case H2_SESSION_ST_KEEPALIVE:
+ /* Our normal H2Timeout has passed and we are considering to
+ * extend that with the H2KeepAliveTimeout. This works different
+ * for async MPMs. */
+ remain_secs = session->keepalive_secs - session->timeout_secs;
+ if (remain_secs <= 0) {
+ /* keepalive is smaller than normal timeout, close the session */
+ reason = "keepalive expired";
+ h2_session_abort_int(session, 0);
+ goto out;
+ }
+ ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_KEEPALIVE, c);
+ if (async && session->c->cs) {
+ /* Async MPMs are able to handle keep-alive connections without
+ * blocking a thread. For this to happen, we need to return from
+ * processing, indicating the IO event we are waiting for, and
+ * may be called again if the event happens.
+ * For now, we let the MPM handle any timing on this, so we
+ * cannot really enforce the remain_secs here.
+ */
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): async KEEPALIVE -> BUSY", session->id);
+ session->state = H2_SESSION_ST_BUSY;
+ session->c->cs->sense = CONN_SENSE_WANT_READ;
+ reason = "async keepalive";
+ status = APR_SUCCESS;
+ goto out;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): KEEPALIVE read", session->id);
+ h2_filter_cin_timeout_set(session->cin, remain_secs);
+ status = h2_session_read(session, 1, 1);
+ if (APR_STATUS_IS_TIMEUP(status)) {
+ reason = "keepalive expired";
+ h2_session_abort_int(session, 0);
+ goto out;
+ }
+ else if (status != APR_SUCCESS) {
+ reason = "keepalive error";
+ goto out;
+ }
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): KEEPALIVE -> BUSY", session->id);
+ session->state = H2_SESSION_ST_BUSY;
+ break;
+
+ case H2_SESSION_ST_CLOSING:
+ if (nghttp2_session_want_write(session->ngh2)) {
+ status = h2_session_send(session);
+ if (status != APR_SUCCESS) {
+ reason = "send error";
+ goto out;
+ }
+ have_written = 1;
+ }
+ reason = "closing";
+ goto out;
+
+ default:
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
+ "h2_session(%ld): state %d", session->id, session->state);
+ return APR_EGENERAL;
}
-
+
+ if (!nghttp2_session_want_read(session->ngh2)
+ && !nghttp2_session_want_write(session->ngh2)) {
+ session->state = H2_SESSION_ST_CLOSING;
+ }
+
if (have_written) {
h2_conn_io_flush(&session->io);
}
}
-
- ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, session->c,
- "h2_session(%ld): process -> return", session->id);
+out:
+ if (have_written) {
+ h2_conn_io_flush(&session->io);
+ }
+ ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, c,
+ "h2_session(%ld): process return, state %d, reason '%s'",
+ session->id, session->state, reason);
return status;
}
Modified: httpd/httpd/trunk/modules/http2/h2_session.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_session.h?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_session.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_session.h Tue Dec 22 15:52:10 2015
@@ -53,9 +53,16 @@ struct h2_workers;
struct nghttp2_session;
-typedef struct h2_session h2_session;
+typedef enum {
+ H2_SESSION_ST_INIT, /* send initial SETTINGS, etc. */
+ H2_SESSION_ST_IDLE_READ, /* nothing to write, expecting data inc */
+ H2_SESSION_ST_BUSY, /* read/write without stop */
+ H2_SESSION_ST_BUSY_WAIT, /* waiting for tasks reporting back */
+ H2_SESSION_ST_KEEPALIVE, /* nothing to write, normal timeout passed */
+ H2_SESSION_ST_CLOSING, /* shuting down */
+} h2_session_state;
-struct h2_session {
+typedef struct h2_session {
long id; /* identifier of this session, unique
* inside a httpd process */
conn_rec *c; /* the connection this session serves */
@@ -64,11 +71,12 @@ struct h2_session {
server_rec *s; /* server/vhost we're starting on */
const struct h2_config *config; /* Relevant config for this session */
- unsigned int started : 1; /* session startup done */
- unsigned int aborted : 1; /* this session is being aborted */
- unsigned int reprioritize : 1; /* scheduled streams priority changed */
-
- apr_interval_time_t wait_micros;
+ h2_session_state state; /* state session is in */
+ unsigned int aborted : 1; /* aborted processing, emergency exit */
+ unsigned int reprioritize : 1; /* scheduled streams priority changed */
+ unsigned int client_goaway : 1; /* client sent us a GOAWAY */
+ apr_interval_time_t wait_us; /* timout during BUSY_WAIT state, micro secs */
+
int unsent_submits; /* number of submitted, but not yet sent
responses. */
int unsent_promises; /* number of submitted, but not yet sent
@@ -104,7 +112,7 @@ struct h2_session {
struct nghttp2_session *ngh2; /* the nghttp2 session (internal use) */
struct h2_workers *workers; /* for executing stream tasks */
-};
+} h2_session;
/**
@@ -138,13 +146,6 @@ h2_session *h2_session_rcreate(request_r
apr_status_t h2_session_process(h2_session *session, int async);
/**
- * Destroy the session and all objects it still contains. This will not
- * destroy h2_task instances that have not finished yet.
- * @param session the session to destroy
- */
-void h2_session_destroy(h2_session *session);
-
-/**
* Cleanup the session and all objects it still contains. This will not
* destroy h2_task instances that have not finished yet.
* @param session the session to destroy
@@ -161,9 +162,9 @@ void h2_session_eoc_callback(h2_session
apr_status_t h2_session_abort(h2_session *session, apr_status_t reason, int rv);
/**
- * Called before a session gets destroyed, might flush output etc.
+ * Close and deallocate the given session.
*/
-apr_status_t h2_session_close(h2_session *session);
+void h2_session_close(h2_session *session);
/* Start submitting the response to a stream request. This is possible
* once we have all the response headers. */
Modified: httpd/httpd/trunk/modules/http2/h2_util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_util.c?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_util.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_util.c Tue Dec 22 15:52:10 2015
@@ -277,7 +277,8 @@ apr_status_t h2_util_move(apr_bucket_bri
AP_DEBUG_ASSERT(to);
AP_DEBUG_ASSERT(from);
- same_alloc = (to->bucket_alloc == from->bucket_alloc);
+ same_alloc = (to->bucket_alloc == from->bucket_alloc
+ || to->p == from->p);
if (!FILE_MOVE) {
pfile_handles_allowed = NULL;
Modified: httpd/httpd/trunk/modules/http2/h2_workers.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_workers.c?rev=1721417&r1=1721416&r2=1721417&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_workers.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_workers.c Tue Dec 22 15:52:10 2015
@@ -51,7 +51,7 @@ static void cleanup_zombies(h2_workers *
while (!H2_WORKER_LIST_EMPTY(&workers->zombies)) {
h2_worker *zombie = H2_WORKER_LIST_FIRST(&workers->zombies);
H2_WORKER_REMOVE(zombie);
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
"h2_workers: cleanup zombie %d", zombie->id);
h2_worker_destroy(zombie);
}
@@ -106,7 +106,7 @@ static apr_status_t get_mplx_next(h2_wor
status = apr_thread_mutex_lock(workers->lock);
if (status == APR_SUCCESS) {
++workers->idle_worker_count;
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
"h2_worker(%d): looking for work", h2_worker_get_id(worker));
while (!task && !h2_worker_is_aborted(worker) && !workers->aborted) {
@@ -147,14 +147,14 @@ static apr_status_t get_mplx_next(h2_wor
if (now >= (start_wait + max_wait)) {
/* waited long enough without getting a task. */
if (workers->worker_count > workers->min_size) {
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0,
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0,
workers->s,
"h2_workers: aborting idle worker");
h2_worker_abort(worker);
break;
}
}
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
"h2_worker(%d): waiting signal, "
"worker_count=%d", worker->id,
(int)workers->worker_count);
@@ -162,7 +162,7 @@ static apr_status_t get_mplx_next(h2_wor
workers->lock, max_wait);
}
else {
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
"h2_worker(%d): waiting signal (eternal), "
"worker_count=%d", worker->id,
(int)workers->worker_count);
@@ -175,7 +175,7 @@ static apr_status_t get_mplx_next(h2_wor
* needed to give up with more than enough workers.
*/
if (task) {
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
"h2_worker(%d): start task(%s)",
h2_worker_get_id(worker), task->id);
/* Since we hand out a reference to the worker, we increase
@@ -206,7 +206,7 @@ static void worker_done(h2_worker *worke
h2_workers *workers = (h2_workers *)ctx;
apr_status_t status = apr_thread_mutex_lock(workers->lock);
if (status == APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
"h2_worker(%d): done", h2_worker_get_id(worker));
H2_WORKER_REMOVE(worker);
--workers->worker_count;
@@ -224,7 +224,7 @@ static apr_status_t add_worker(h2_worker
if (!w) {
return APR_ENOMEM;
}
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
"h2_workers: adding worker(%d)", h2_worker_get_id(w));
++workers->worker_count;
H2_WORKER_LIST_INSERT_TAIL(&workers->workers, w);
@@ -335,7 +335,7 @@ apr_status_t h2_workers_register(h2_work
{
apr_status_t status = apr_thread_mutex_lock(workers->lock);
if (status == APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_TRACE2, status, workers->s,
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, status, workers->s,
"h2_workers: register mplx(%ld)", m->id);
if (in_list(workers, m)) {
status = APR_EAGAIN;
@@ -349,7 +349,7 @@ apr_status_t h2_workers_register(h2_work
apr_thread_cond_signal(workers->mplx_added);
}
else if (workers->worker_count < workers->max_size) {
- ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
"h2_workers: got %d worker, adding 1",
workers->worker_count);
add_worker(workers);