summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--changes-entries/h2_workers_dynamic.txt13
-rw-r--r--modules/http2/h2_mplx.c32
-rw-r--r--modules/http2/h2_request.c105
-rw-r--r--modules/http2/h2_session.c1
-rw-r--r--modules/http2/h2_task.c2
-rw-r--r--modules/http2/h2_version.h4
-rw-r--r--modules/http2/h2_workers.c190
-rw-r--r--modules/http2/h2_workers.h8
-rw-r--r--modules/http2/mod_proxy_http2.c11
9 files changed, 282 insertions, 84 deletions
diff --git a/changes-entries/h2_workers_dynamic.txt b/changes-entries/h2_workers_dynamic.txt
new file mode 100644
index 0000000000..1d3115c724
--- /dev/null
+++ b/changes-entries/h2_workers_dynamic.txt
@@ -0,0 +1,13 @@
+ *) mod_http2:
+ - Aborting requests via RST_STREAM no longer affect the available
+ resources of a connection when the first chunk of the response
+ body has been sent.
+ - H2Min/MaxWorkers behave as intended again. The module will initially
+ create H2MinWorkers threads and add up to H2MaxWorkers when needed. These
+ additional workers time out when idle after H2MaxWorkerIdleSeconds and
+ disappear again.
+ - When the shutdown of a child is detected (e.g. graceful shutdown), the
+ module will terminate all idle workers above H2MinWorkers right away.
+ This detection currently only happens when a HTTP/2 connection is active.
+ [Stefan Eissing]
+
diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c
index d787e0d09d..7ab5ec9300 100644
--- a/modules/http2/h2_mplx.c
+++ b/modules/http2/h2_mplx.c
@@ -739,6 +739,12 @@ static h2_task *s_next_stream_task(h2_mplx *m)
return stream->task;
}
}
+ if (m->tasks_active >= m->limit_active && !h2_iq_empty(m->q)) {
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
+ "h2_session(%ld): delaying request processing. "
+ "Current limit is %d and %d workers are in use.",
+ m->id, m->limit_active, m->tasks_active);
+ }
return NULL;
}
@@ -1132,14 +1138,36 @@ int h2_mplx_m_awaits_data(h2_mplx *m)
return waiting;
}
+static int reset_is_acceptable(h2_stream *stream)
+{
+ /* client may terminate a stream via H2 RST_STREAM message at any time.
+ * This is annyoing when we have committed resources (e.g. worker threads)
+ * to it, so our mood (e.g. willingness to commit resources on this
+ * connection in the future) goes down.
+ *
+ * This is a DoS protection. We do not want to make it too easy for
+ * a client to eat up server resources.
+ *
+ * However: there are cases where a RST_STREAM is the only way to end
+ * a request. This includes websockets and server-side-event streams (SSEs).
+ * The responses to such requests continue forever otherwise.
+ *
+ */
+ if (!stream->task) return 1; /* have not started or already ended for us. acceptable. */
+ if (!(stream->id & 0x01)) return 1; /* stream initiated by us. acceptable. */
+ if (!stream->has_response) return 0; /* no response headers produced yet. bad. */
+ if (!stream->out_data_frames) return 0; /* no response body data sent yet. bad. */
+ return 1; /* otherwise, be forgiving */
+}
+
apr_status_t h2_mplx_m_client_rst(h2_mplx *m, int stream_id)
{
h2_stream *stream;
apr_status_t status = APR_SUCCESS;
-
+
H2_MPLX_ENTER_ALWAYS(m);
stream = h2_ihash_get(m->streams, stream_id);
- if (stream && stream->task) {
+ if (stream && !reset_is_acceptable(stream)) {
status = m_be_annoyed(m);
}
H2_MPLX_LEAVE(m);
diff --git a/modules/http2/h2_request.c b/modules/http2/h2_request.c
index 5adf84151e..7c4fb95ea4 100644
--- a/modules/http2/h2_request.c
+++ b/modules/http2/h2_request.c
@@ -210,12 +210,74 @@ h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src)
return dst;
}
+#if !AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
+static request_rec *my_ap_create_request(conn_rec *c)
+{
+ apr_pool_t *p;
+ request_rec *r;
+
+ apr_pool_create(&p, c->pool);
+ apr_pool_tag(p, "request");
+ r = apr_pcalloc(p, sizeof(request_rec));
+ AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)c);
+ r->pool = p;
+ r->connection = c;
+ r->server = c->base_server;
+
+ r->user = NULL;
+ r->ap_auth_type = NULL;
+
+ r->allowed_methods = ap_make_method_list(p, 2);
+
+ r->headers_in = apr_table_make(r->pool, 5);
+ r->trailers_in = apr_table_make(r->pool, 5);
+ r->subprocess_env = apr_table_make(r->pool, 25);
+ r->headers_out = apr_table_make(r->pool, 12);
+ r->err_headers_out = apr_table_make(r->pool, 5);
+ r->trailers_out = apr_table_make(r->pool, 5);
+ r->notes = apr_table_make(r->pool, 5);
+
+ r->request_config = ap_create_request_config(r->pool);
+ /* Must be set before we run create request hook */
+
+ r->proto_output_filters = c->output_filters;
+ r->output_filters = r->proto_output_filters;
+ r->proto_input_filters = c->input_filters;
+ r->input_filters = r->proto_input_filters;
+ ap_run_create_request(r);
+ r->per_dir_config = r->server->lookup_defaults;
+
+ r->sent_bodyct = 0; /* bytect isn't for body */
+
+ r->read_length = 0;
+ r->read_body = REQUEST_NO_BODY;
+
+ r->status = HTTP_OK; /* Until further notice */
+ r->header_only = 0;
+ r->the_request = NULL;
+
+ /* Begin by presuming any module can make its own path_info assumptions,
+ * until some module interjects and changes the value.
+ */
+ r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
+
+ r->useragent_addr = c->client_addr;
+ r->useragent_ip = c->client_ip;
+ return r;
+}
+#endif
+
request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
{
int access_status = HTTP_OK;
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
request_rec *r = ap_create_request(c);
+#else
+ request_rec *r = my_ap_create_request(c);
+#endif
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 107)
ap_run_pre_read_request(r, c);
/* Time to populate r with the data we have. */
@@ -244,6 +306,49 @@ request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
r->status = HTTP_OK;
goto die;
}
+#else
+ {
+ const char *s;
+
+ r->headers_in = apr_table_clone(r->pool, req->headers);
+ ap_run_pre_read_request(r, c);
+
+ /* Time to populate r with the data we have. */
+ r->request_time = req->request_time;
+ r->method = apr_pstrdup(r->pool, req->method);
+ /* Provide quick information about the request method as soon as known */
+ r->method_number = ap_method_number_of(r->method);
+ if (r->method_number == M_GET && r->method[0] == 'H') {
+ r->header_only = 1;
+ }
+ ap_parse_uri(r, req->path ? req->path : "");
+ r->protocol = (char*)"HTTP/2.0";
+ r->proto_num = HTTP_VERSION(2, 0);
+ r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+ r->method, req->path ? req->path : "");
+
+ /* Start with r->hostname = NULL, ap_check_request_header() will get it
+ * form Host: header, otherwise we get complains about port numbers.
+ */
+ r->hostname = NULL;
+ ap_update_vhost_from_headers(r);
+
+ /* we may have switched to another server */
+ r->per_dir_config = r->server->lookup_defaults;
+
+ s = apr_table_get(r->headers_in, "Expect");
+ if (s && s[0]) {
+ if (ap_cstr_casecmp(s, "100-continue") == 0) {
+ r->expecting_100 = 1;
+ }
+ else {
+ r->status = HTTP_EXPECTATION_FAILED;
+ access_status = r->status;
+ goto die;
+ }
+ }
+ }
+#endif
/* we may have switched to another server */
r->per_dir_config = r->server->lookup_defaults;
diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c
index 1915855792..4cc2c96be3 100644
--- a/modules/http2/h2_session.c
+++ b/modules/http2/h2_session.c
@@ -1908,6 +1908,7 @@ static void h2_session_ev_mpm_stopping(h2_session *session, int arg, const char
break;
default:
h2_session_shutdown_notice(session);
+ h2_workers_graceful_shutdown(session->workers);
break;
}
}
diff --git a/modules/http2/h2_task.c b/modules/http2/h2_task.c
index 399c40b60f..5b32656a91 100644
--- a/modules/http2/h2_task.c
+++ b/modules/http2/h2_task.c
@@ -593,7 +593,7 @@ apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread, int worker_id)
* configurations by mod_h2 alone.
*/
task->c->id = (c->master->id << 8)^worker_id;
- task->id = apr_psprintf(task->pool, "%ld-%d", c->master->id,
+ task->id = apr_psprintf(task->pool, "%ld-%d", task->mplx->id,
task->stream_id);
}
diff --git a/modules/http2/h2_version.h b/modules/http2/h2_version.h
index c8b1106439..38a2190444 100644
--- a/modules/http2/h2_version.h
+++ b/modules/http2/h2_version.h
@@ -27,7 +27,7 @@
* @macro
* Version number of the http2 module as c string
*/
-#define MOD_HTTP2_VERSION "1.15.18"
+#define MOD_HTTP2_VERSION "1.15.21"
/**
* @macro
@@ -35,7 +35,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 0x010f12
+#define MOD_HTTP2_VERSION_NUM 0x010f15
#endif /* mod_h2_h2_version_h */
diff --git a/modules/http2/h2_workers.c b/modules/http2/h2_workers.c
index bb64a1a506..7832ac0e38 100644
--- a/modules/http2/h2_workers.c
+++ b/modules/http2/h2_workers.c
@@ -41,6 +41,7 @@ struct h2_slot {
apr_thread_t *thread;
apr_thread_mutex_t *lock;
apr_thread_cond_t *not_idle;
+ volatile apr_uint32_t timed_out;
};
static h2_slot *pop_slot(h2_slot *volatile *phead)
@@ -71,47 +72,47 @@ static void push_slot(h2_slot *volatile *phead, h2_slot *slot)
}
static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx);
+static void slot_done(h2_slot *slot);
static apr_status_t activate_slot(h2_workers *workers, h2_slot *slot)
{
- apr_status_t status;
+ apr_status_t rv;
slot->workers = workers;
slot->task = NULL;
+ apr_thread_mutex_lock(workers->lock);
if (!slot->lock) {
- status = apr_thread_mutex_create(&slot->lock,
+ rv = apr_thread_mutex_create(&slot->lock,
APR_THREAD_MUTEX_DEFAULT,
workers->pool);
- if (status != APR_SUCCESS) {
- push_slot(&workers->free, slot);
- return status;
- }
+ if (rv != APR_SUCCESS) goto cleanup;
}
if (!slot->not_idle) {
- status = apr_thread_cond_create(&slot->not_idle, workers->pool);
- if (status != APR_SUCCESS) {
- push_slot(&workers->free, slot);
- return status;
- }
+ rv = apr_thread_cond_create(&slot->not_idle, workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
}
- ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, workers->s,
+ ap_log_error(APLOG_MARK, APLOG_WARNING, 0, workers->s,
"h2_workers: new thread for slot %d", slot->id);
/* thread will either immediately start work or add itself
* to the idle queue */
apr_atomic_inc32(&workers->worker_count);
- status = apr_thread_create(&slot->thread, workers->thread_attr,
+ slot->timed_out = 0;
+ rv = apr_thread_create(&slot->thread, workers->thread_attr,
slot_run, slot, workers->pool);
- if (status != APR_SUCCESS) {
+ if (rv != APR_SUCCESS) {
apr_atomic_dec32(&workers->worker_count);
+ }
+
+cleanup:
+ apr_thread_mutex_unlock(workers->lock);
+ if (rv != APR_SUCCESS) {
push_slot(&workers->free, slot);
- return status;
}
-
- return APR_SUCCESS;
+ return rv;
}
static apr_status_t add_worker(h2_workers *workers)
@@ -127,11 +128,19 @@ static void wake_idle_worker(h2_workers *workers)
{
h2_slot *slot = pop_slot(&workers->idle);
if (slot) {
+ int timed_out = 0;
apr_thread_mutex_lock(slot->lock);
- apr_thread_cond_signal(slot->not_idle);
+ timed_out = slot->timed_out;
+ if (!timed_out) {
+ apr_thread_cond_signal(slot->not_idle);
+ }
apr_thread_mutex_unlock(slot->lock);
+ if (timed_out) {
+ slot_done(slot);
+ wake_idle_worker(workers);
+ }
}
- else if (workers->dynamic) {
+ else if (workers->dynamic && !workers->shutdown) {
add_worker(workers);
}
}
@@ -185,13 +194,18 @@ static h2_fifo_op_t mplx_peek(void *head, void *ctx)
static int get_next(h2_slot *slot)
{
h2_workers *workers = slot->workers;
+ int non_essential = slot->id >= workers->min_workers;
+ apr_status_t rv;
- while (!workers->aborted) {
+ while (!workers->aborted && !slot->timed_out) {
ap_assert(slot->task == NULL);
+ if (non_essential && workers->shutdown) {
+ /* Terminate non-essential worker on shutdown */
+ break;
+ }
if (h2_fifo_try_peek(workers->mplxs, mplx_peek, slot) == APR_EOF) {
/* The queue is terminated with the MPM child being cleaned up,
- * just leave.
- */
+ * just leave. */
break;
}
if (slot->task) {
@@ -202,8 +216,18 @@ static int get_next(h2_slot *slot)
apr_thread_mutex_lock(slot->lock);
if (!workers->aborted) {
+
push_slot(&workers->idle, slot);
- apr_thread_cond_wait(slot->not_idle, slot->lock);
+ if (non_essential && workers->max_idle_duration) {
+ rv = apr_thread_cond_timedwait(slot->not_idle, slot->lock,
+ workers->max_idle_duration);
+ if (APR_TIMEUP == rv) {
+ slot->timed_out = 1;
+ }
+ }
+ else {
+ apr_thread_cond_wait(slot->not_idle, slot->lock);
+ }
}
apr_thread_mutex_unlock(slot->lock);
}
@@ -251,17 +275,36 @@ static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx)
} while (slot->task);
}
- slot_done(slot);
+ if (!slot->timed_out) {
+ slot_done(slot);
+ }
apr_thread_exit(thread, APR_SUCCESS);
return NULL;
}
-static apr_status_t workers_pool_cleanup(void *data)
+static void wake_non_essential_workers(h2_workers *workers)
{
- h2_workers *workers = data;
h2_slot *slot;
-
+ /* pop all idle, signal the non essentials and add the others again */
+ if ((slot = pop_slot(&workers->idle))) {
+ wake_non_essential_workers(workers);
+ if (slot->id > workers->min_workers) {
+ apr_thread_mutex_lock(slot->lock);
+ apr_thread_cond_signal(slot->not_idle);
+ apr_thread_mutex_unlock(slot->lock);
+ }
+ else {
+ push_slot(&workers->idle, slot);
+ }
+ }
+}
+
+static void workers_abort_idle(h2_workers *workers)
+{
+ h2_slot *slot;
+
+ workers->shutdown = 1;
workers->aborted = 1;
h2_fifo_term(workers->mplxs);
@@ -271,6 +314,13 @@ static apr_status_t workers_pool_cleanup(void *data)
apr_thread_cond_signal(slot->not_idle);
apr_thread_mutex_unlock(slot->lock);
}
+}
+
+static apr_status_t workers_pool_cleanup(void *data)
+{
+ h2_workers *workers = data;
+
+ workers_abort_idle(workers);
/* wait for all the workers to become zombies and join them */
apr_thread_mutex_lock(workers->lock);
@@ -287,7 +337,7 @@ h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pchild,
int min_workers, int max_workers,
int idle_secs)
{
- apr_status_t status;
+ apr_status_t rv;
h2_workers *workers;
apr_pool_t *pool;
int i, n;
@@ -311,7 +361,7 @@ h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pchild,
workers->pool = pool;
workers->min_workers = min_workers;
workers->max_workers = max_workers;
- workers->max_idle_secs = (idle_secs > 0)? idle_secs : 10;
+ workers->max_idle_duration = apr_time_from_sec((idle_secs > 0)? idle_secs : 10);
/* FIXME: the fifo set we use here has limited capacity. Once the
* set is full, connections with new requests do a wait. Unfortunately,
@@ -324,16 +374,12 @@ h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pchild,
* For now, we just make enough room to have many connections inside one
* process.
*/
- status = h2_fifo_set_create(&workers->mplxs, pool, 8 * 1024);
- if (status != APR_SUCCESS) {
- return NULL;
- }
-
- status = apr_threadattr_create(&workers->thread_attr, workers->pool);
- if (status != APR_SUCCESS) {
- return NULL;
- }
-
+ rv = h2_fifo_set_create(&workers->mplxs, pool, 8 * 1024);
+ if (rv != APR_SUCCESS) goto cleanup;
+
+ rv = apr_threadattr_create(&workers->thread_attr, workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+
if (ap_thread_stacksize != 0) {
apr_threadattr_stacksize_set(workers->thread_attr,
ap_thread_stacksize);
@@ -342,38 +388,39 @@ h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pchild,
(long)ap_thread_stacksize);
}
- status = apr_thread_mutex_create(&workers->lock,
- APR_THREAD_MUTEX_DEFAULT,
- workers->pool);
- if (status == APR_SUCCESS) {
- status = apr_thread_cond_create(&workers->all_done, workers->pool);
+ rv = apr_thread_mutex_create(&workers->lock,
+ APR_THREAD_MUTEX_DEFAULT,
+ workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+ rv = apr_thread_cond_create(&workers->all_done, workers->pool);
+ if (rv != APR_SUCCESS) goto cleanup;
+
+ n = workers->nslots = workers->max_workers;
+ workers->slots = apr_pcalloc(workers->pool, n * sizeof(h2_slot));
+ if (workers->slots == NULL) {
+ n = workers->nslots = 0;
+ rv = APR_ENOMEM;
+ goto cleanup;
}
- if (status == APR_SUCCESS) {
- n = workers->nslots = workers->max_workers;
- workers->slots = apr_pcalloc(workers->pool, n * sizeof(h2_slot));
- if (workers->slots == NULL) {
- n = workers->nslots = 0;
- status = APR_ENOMEM;
- }
- for (i = 0; i < n; ++i) {
- workers->slots[i].id = i;
- }
+ for (i = 0; i < n; ++i) {
+ workers->slots[i].id = i;
}
- if (status == APR_SUCCESS) {
- /* we activate all for now, TODO: support min_workers again.
- * do this in reverse for vanity reasons so slot 0 will most
- * likely be at head of idle queue. */
- n = workers->max_workers;
- for (i = n-1; i >= 0; --i) {
- status = activate_slot(workers, &workers->slots[i]);
- }
- /* the rest of the slots go on the free list */
- for(i = n; i < workers->nslots; ++i) {
- push_slot(&workers->free, &workers->slots[i]);
- }
- workers->dynamic = (workers->worker_count < workers->max_workers);
+ /* we activate all for now, TODO: support min_workers again.
+ * do this in reverse for vanity reasons so slot 0 will most
+ * likely be at head of idle queue. */
+ n = workers->min_workers;
+ for (i = n-1; i >= 0; --i) {
+ rv = activate_slot(workers, &workers->slots[i]);
+ if (rv != APR_SUCCESS) goto cleanup;
+ }
+ /* the rest of the slots go on the free list */
+ for(i = n; i < workers->nslots; ++i) {
+ push_slot(&workers->free, &workers->slots[i]);
}
- if (status == APR_SUCCESS) {
+ workers->dynamic = (workers->worker_count < workers->max_workers);
+
+cleanup:
+ if (rv == APR_SUCCESS) {
/* Stop/join the workers threads when the MPM child exits (pchild is
* destroyed), and as a pre_cleanup of pchild thus before the threads
* pools (children of workers->pool) so that they are not destroyed
@@ -396,3 +443,10 @@ apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m)
{
return h2_fifo_remove(workers->mplxs, m);
}
+
+void h2_workers_graceful_shutdown(h2_workers *workers)
+{
+ workers->shutdown = 1;
+ h2_fifo_term(workers->mplxs);
+ wake_non_essential_workers(workers);
+}
diff --git a/modules/http2/h2_workers.h b/modules/http2/h2_workers.h
index 6298c81397..cc310c9b75 100644
--- a/modules/http2/h2_workers.h
+++ b/modules/http2/h2_workers.h
@@ -40,9 +40,10 @@ struct h2_workers {
int next_worker_id;
apr_uint32_t min_workers;
apr_uint32_t max_workers;
- int max_idle_secs;
+ apr_interval_time_t max_idle_duration;
volatile int aborted;
+ volatile int shutdown;
int dynamic;
apr_threadattr_t *thread_attr;
@@ -80,4 +81,9 @@ apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m);
*/
apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m);
+/**
+ * Shut down processing gracefully by terminating all idle workers.
+ */
+void h2_workers_graceful_shutdown(h2_workers *workers);
+
#endif /* defined(__mod_h2__h2_workers__) */
diff --git a/modules/http2/mod_proxy_http2.c b/modules/http2/mod_proxy_http2.c
index 893aa8fd31..4ea4fb9741 100644
--- a/modules/http2/mod_proxy_http2.c
+++ b/modules/http2/mod_proxy_http2.c
@@ -397,18 +397,9 @@ run_connect:
if (!ctx->p_conn->data && ctx->is_ssl) {
/* New SSL connection: set a note on the connection about what
- * protocol we want.
- */
+ * protocol we need. */
apr_table_setn(ctx->p_conn->connection->notes,
"proxy-request-alpn-protos", "h2");
- if (ctx->p_conn->ssl_hostname) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctx->owner,
- "set SNI to %s for (%s)",
- ctx->p_conn->ssl_hostname,
- ctx->p_conn->hostname);
- apr_table_setn(ctx->p_conn->connection->notes,
- "proxy-request-hostname", ctx->p_conn->ssl_hostname);
- }
}
if (ctx->master->aborted) goto cleanup;