+/* Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
*
* Licensed under the Apache License, Version 2.0 (the "License");
*/
#include <assert.h>
+#include <apr_strings.h>
#include <ap_mpm.h>
#include <http_protocol.h>
#include <http_request.h>
+#include <mpm_common.h>
+
#include "h2_private.h"
#include "h2.h"
#include "h2_config.h"
#include "h2_stream.h"
#include "h2_h2.h"
#include "h2_task.h"
-#include "h2_worker.h"
#include "h2_workers.h"
#include "h2_conn.h"
#include "h2_version.h"
static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN;
static module *mpm_module;
static int async_mpm;
+static int mpm_supported = 1;
static apr_socket_t *dummy_socket;
static void check_modules(int force)
else if (!strcmp("prefork.c", m->name)) {
mpm_type = H2_MPM_PREFORK;
mpm_module = m;
+ /* While http2 can work really well on prefork, it collides
+ * today's use case for prefork: runnning single-thread app engines
+ * like php. If we restrict h2_workers to 1 per process, php will
+ * work fine, but browser will be limited to 1 active request at a
+ * time. */
+ mpm_supported = 0;
break;
}
else if (!strcmp("simple_api.c", m->name)) {
mpm_type = H2_MPM_SIMPLE;
mpm_module = m;
+ mpm_supported = 0;
break;
}
else if (!strcmp("mpm_winnt.c", m->name)) {
{
const h2_config *config = h2_config_sget(s);
apr_status_t status = APR_SUCCESS;
- int minw, maxw, max_tx_handles, n;
+ int minw, maxw;
int max_threads_per_child = 0;
int idle_secs = 0;
check_modules(1);
-
ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads_per_child);
status = ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm);
minw = max_threads_per_child;
}
if (maxw <= 0) {
- maxw = minw;
+ /* As a default, this seems to work quite well under mpm_event.
+ * For people enabling http2 under mpm_prefork, start 4 threads unless
+ * configured otherwise. People get unhappy if their http2 requests are
+ * blocking each other. */
+ maxw = H2MAX(3 * minw / 2, 4);
}
- /* How many file handles is it safe to use for transfer
- * to the master connection to be streamed out?
- * Is there a portable APR rlimit on NOFILES? Have not
- * found it. And if, how many of those would we set aside?
- * This leads all into a process wide handle allocation strategy
- * which ultimately would limit the number of accepted connections
- * with the assumption of implicitly reserving n handles for every
- * connection and requiring modules with excessive needs to allocate
- * from a central pool.
- */
- n = h2_config_geti(config, H2_CONF_SESSION_FILES);
- if (n < 0) {
- max_tx_handles = maxw * 2;
- }
- else {
- max_tx_handles = maxw * n;
- }
-
- ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
- "h2_workers: min=%d max=%d, mthrpchild=%d, tx_files=%d",
- minw, maxw, max_threads_per_child, max_tx_handles);
- workers = h2_workers_create(s, pool, minw, maxw, max_tx_handles);
-
idle_secs = h2_config_geti(config, H2_CONF_MAX_WORKER_IDLE_SECS);
- h2_workers_set_max_idle_secs(workers, idle_secs);
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
+ "h2_workers: min=%d max=%d, mthrpchild=%d, idle_secs=%d",
+ minw, maxw, max_threads_per_child, idle_secs);
+ workers = h2_workers_create(s, pool, minw, maxw, idle_secs);
ap_register_input_filter("H2_IN", h2_filter_core_input,
NULL, AP_FTYPE_CONNECTION);
return mpm_type;
}
+const char *h2_conn_mpm_name(void)
+{
+ check_modules(0);
+ return mpm_module? mpm_module->name : "unknown";
+}
+
+int h2_mpm_supported(void)
+{
+ check_modules(0);
+ return mpm_supported;
+}
+
static module *h2_conn_mpm_module(void)
{
check_modules(0);
apr_status_t h2_conn_setup(h2_ctx *ctx, conn_rec *c, request_rec *r)
{
h2_session *session;
+ apr_status_t status;
if (!workers) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911)
}
if (r) {
- session = h2_session_rcreate(r, ctx, workers);
+ status = h2_session_rcreate(&session, r, ctx, workers);
}
else {
- session = h2_session_create(c, ctx, workers);
+ status = h2_session_create(&session, c, ctx, workers);
}
- h2_ctx_session_set(ctx, session);
-
- return APR_SUCCESS;
+ if (status == APR_SUCCESS) {
+ h2_ctx_session_set(ctx, session);
+ }
+ return status;
}
apr_status_t h2_conn_run(struct h2_ctx *ctx, conn_rec *c)
{
apr_status_t status;
int mpm_state = 0;
+ h2_session *session = h2_ctx_session_get(ctx);
+ ap_assert(session);
do {
if (c->cs) {
c->cs->sense = CONN_SENSE_DEFAULT;
+ c->cs->state = CONN_STATE_HANDLER;
}
- status = h2_session_process(h2_ctx_session_get(ctx), async_mpm);
+
+ status = h2_session_process(session, async_mpm);
if (APR_STATUS_IS_EOF(status)) {
- ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03045)
- "h2_session(%ld): process, closing conn", c->id);
+ ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
+ H2_SSSN_LOG(APLOGNO(03045), session,
+ "process, closing conn"));
c->keepalive = AP_CONN_CLOSE;
}
else {
} while (!async_mpm
&& c->keepalive == AP_CONN_KEEPALIVE
&& mpm_state != AP_MPMQ_STOPPING);
-
- return DONE;
+
+ if (c->cs) {
+ c->cs->state = CONN_STATE_LINGER;
+ }
+
+ return APR_SUCCESS;
}
apr_status_t h2_conn_pre_close(struct h2_ctx *ctx, conn_rec *c)
{
- apr_status_t status;
-
- status = h2_session_pre_close(h2_ctx_session_get(ctx), async_mpm);
- if (status == APR_SUCCESS) {
- return DONE; /* This is the same, right? */
+ h2_session *session = h2_ctx_session_get(ctx);
+ if (session) {
+ apr_status_t status = h2_session_pre_close(session, async_mpm);
+ return (status == APR_SUCCESS)? DONE : status;
}
- return status;
+ return DONE;
}
-conn_rec *h2_slave_create(conn_rec *master, apr_pool_t *parent,
- apr_allocator_t *allocator)
+conn_rec *h2_slave_create(conn_rec *master, int slave_id, apr_pool_t *parent)
{
+ apr_allocator_t *allocator;
+ apr_status_t status;
apr_pool_t *pool;
conn_rec *c;
void *cfg;
+ module *mpm;
- AP_DEBUG_ASSERT(master);
+ ap_assert(master);
ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, master,
- "h2_conn(%ld): create slave", master->id);
+ "h2_stream(%ld-%d): create slave", master->id, slave_id);
/* We create a pool with its own allocator to be used for
* processing a request. This is the only way to have the processing
* independant of its parent pool in the sense that it can work in
- * another thread.
+ * another thread. Also, the new allocator needs its own mutex to
+ * synchronize sub-pools.
*/
- if (!allocator) {
- apr_allocator_create(&allocator);
+ apr_allocator_create(&allocator);
+ apr_allocator_max_free_set(allocator, ap_max_mem_free);
+ status = apr_pool_create_ex(&pool, parent, NULL, allocator);
+ if (status != APR_SUCCESS) {
+ ap_log_cerror(APLOG_MARK, APLOG_ERR, status, master,
+ APLOGNO(10004) "h2_session(%ld-%d): create slave pool",
+ master->id, slave_id);
+ return NULL;
}
- apr_pool_create_ex(&pool, parent, NULL, allocator);
- apr_pool_tag(pool, "h2_slave_conn");
apr_allocator_owner_set(allocator, pool);
-
+ apr_pool_tag(pool, "h2_slave_conn");
+
c = (conn_rec *) apr_palloc(pool, sizeof(conn_rec));
if (c == NULL) {
ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, master,
- APLOGNO(02913) "h2_task: creating conn");
+ APLOGNO(02913) "h2_session(%ld-%d): create slave",
+ master->id, slave_id);
+ apr_pool_destroy(pool);
return NULL;
}
memcpy(c, master, sizeof(conn_rec));
-
- /* Replace these */
+
c->master = master;
c->pool = pool;
c->conn_config = ap_create_conn_config(pool);
c->bucket_alloc = apr_bucket_alloc_create(pool);
c->data_in_input_filters = 0;
c->data_in_output_filters = 0;
+ /* prevent mpm_event from making wrong assumptions about this connection,
+ * like e.g. using its socket for an async read check. */
c->clogging_input_filters = 1;
c->log = NULL;
- c->log_id = NULL;
+ c->log_id = apr_psprintf(pool, "%ld-%d",
+ master->id, slave_id);
/* Simulate that we had already a request on this connection. */
c->keepalives = 1;
+ c->aborted = 0;
/* We cannot install the master connection socket on the slaves, as
* modules mess with timeouts/blocking of the socket, with
* unwanted side effects to the master connection processing.
/* TODO: not all mpm modules have learned about slave connections yet.
* copy their config from master to slave.
*/
- if (h2_conn_mpm_module()) {
- cfg = ap_get_module_config(master->conn_config, h2_conn_mpm_module());
- ap_set_module_config(c->conn_config, h2_conn_mpm_module(), cfg);
+ if ((mpm = h2_conn_mpm_module()) != NULL) {
+ cfg = ap_get_module_config(master->conn_config, mpm);
+ ap_set_module_config(c->conn_config, mpm, cfg);
}
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
+ "h2_stream(%ld-%d): created slave", master->id, slave_id);
return c;
}
-void h2_slave_destroy(conn_rec *slave, apr_allocator_t **pallocator)
+void h2_slave_destroy(conn_rec *slave)
{
- apr_pool_t *parent;
- apr_allocator_t *allocator = apr_pool_allocator_get(slave->pool);
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, slave,
- "h2_slave_conn(%ld): destroy (task=%s)", slave->id,
+ "h2_stream(%s): destroy slave",
apr_table_get(slave->notes, H2_TASK_ID_NOTE));
- /* Attache the allocator to the parent pool and return it for
- * reuse, otherwise the own is still the slave pool and it will
- * get destroyed with it. */
- parent = apr_pool_parent_get(slave->pool);
- if (pallocator && parent) {
- apr_allocator_owner_set(allocator, parent);
- *pallocator = allocator;
- }
+ slave->sbh = NULL;
apr_pool_destroy(slave->pool);
}