-*- coding: utf-8 -*-
Changes with Apache 2.5.0
+ *) mod_http2: obsoleted option H2SessionExtraFiles, will be ignored and
+ just log a warning. [Stefan Eissing]
+
*) mod_autoindex: Add IndexOptions UseOldDateFormat to allow the date
format from 2.2 in the Last Modified column. PR60846.
[Hank Ibell <hwibell gmail.com>]
the connection will buffer this amount of data and then suspend the
H2Worker.
</p>
- <p>
- If you serve a lot of static files, <directive module="mod_http2">H2SessionExtraFiles</directive>
- is of interest. This tells the server how many file handles per
- HTTP/2 connection it is allowed to waste for better performance. Because
- when a request produces a static file as the response, the file handle
- gets passed around and is buffered and not the file contents. That allows
- to serve many large files without wasting memory or copying data
- unnecessarily. However file handles are a limited resource for a process,
- and if too many are used this way, requests may fail under load as
- the amount of open handles has been exceeded.
- </p>
</section>
<section id="misdirected"><title>Multiple Hosts and Misdirected Requests</title>
</usage>
</directivesynopsis>
- <directivesynopsis>
- <name>H2SessionExtraFiles</name>
- <description>Number of Extra File Handles</description>
- <syntax>H2SessionExtraFiles <em>n</em></syntax>
- <contextlist>
- <context>server config</context>
- <context>virtual host</context>
- </contextlist>
- <usage>
- <p>
- This directive sets maximum number of <em>extra</em> file handles
- a HTTP/2 session is allowed to use. A file handle is counted as
- <em>extra</em> when it is transferred from a h2 worker thread to
- the main HTTP/2 connection handling. This commonly happens when
- serving static files.
- </p><p>
- Depending on the processing model configured on the server, the
- number of connections times number of active streams may exceed
- the number of file handles for the process. On the other hand,
- converting every file into memory bytes early results in too
- many buffer writes. This option helps to mitigate that.
- </p><p>
- The number of file handles used by a server process is then in
- the order of:
- </p>
- <pre>
- (h2_connections * extra_files) + (h2_max_worker)
- </pre>
- <example><title>Example</title>
- <highlight language="config">
-H2SessionExtraFiles 10
- </highlight>
- </example>
- <p>
- If nothing is configured, the module tries to make a conservative
- guess how many files are safe to use. This depends largely on the
- MPM chosen.
- </p>
- </usage>
- </directivesynopsis>
-
<directivesynopsis>
<name>H2SerializeHeaders</name>
<description>Serialize Request/Response Processing Switch</description>
static void beam_set_send_pool(h2_bucket_beam *beam, apr_pool_t *pool)
{
- if (beam->send_pool == pool ||
- (beam->send_pool && pool
- && apr_pool_is_ancestor(beam->send_pool, pool))) {
- /* when sender is same or sub-pool of existing, stick
- * to the the pool we already have. */
- return;
+ if (beam->send_pool != pool) {
+ if (beam->send_pool && beam->send_pool != beam->pool) {
+ pool_kill(beam, beam->send_pool, beam_send_cleanup);
+ beam_send_cleanup(beam);
+ }
+ beam->send_pool = pool;
+ pool_register(beam, beam->send_pool, beam_send_cleanup);
}
- pool_kill(beam, beam->send_pool, beam_send_cleanup);
- beam->send_pool = pool;
- pool_register(beam, beam->send_pool, beam_send_cleanup);
}
static apr_status_t beam_cleanup(void *data)
/* Called from the sender thread to add buckets to the beam */
if (enter_yellow(beam, &bl) == APR_SUCCESS) {
+ ap_assert(beam->send_pool);
r_purge_sent(beam);
- if (sender_bb && !beam->send_pool) {
- beam_set_send_pool(beam, sender_bb->p);
- }
if (beam->aborted) {
move_to_hold(beam, sender_bb);
-1, /* alt-svc max age */
0, /* serialize headers */
-1, /* h2 direct mode */
- -1, /* # session extra files */
1, /* modern TLS only */
-1, /* HTTP/1 Upgrade support */
1024*1024, /* TLS warmup size */
conf->alt_svc_max_age = DEF_VAL;
conf->serialize_headers = DEF_VAL;
conf->h2_direct = DEF_VAL;
- conf->session_extra_files = DEF_VAL;
conf->modern_tls_only = DEF_VAL;
conf->h2_upgrade = DEF_VAL;
conf->tls_warmup_size = DEF_VAL;
n->alt_svc_max_age = H2_CONFIG_GET(add, base, alt_svc_max_age);
n->serialize_headers = H2_CONFIG_GET(add, base, serialize_headers);
n->h2_direct = H2_CONFIG_GET(add, base, h2_direct);
- n->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files);
n->modern_tls_only = H2_CONFIG_GET(add, base, modern_tls_only);
n->h2_upgrade = H2_CONFIG_GET(add, base, h2_upgrade);
n->tls_warmup_size = H2_CONFIG_GET(add, base, tls_warmup_size);
return H2_CONFIG_GET(conf, &defconf, h2_upgrade);
case H2_CONF_DIRECT:
return H2_CONFIG_GET(conf, &defconf, h2_direct);
- case H2_CONF_SESSION_FILES:
- return H2_CONFIG_GET(conf, &defconf, session_extra_files);
case H2_CONF_TLS_WARMUP_SIZE:
return H2_CONFIG_GET(conf, &defconf, tls_warmup_size);
case H2_CONF_TLS_COOLDOWN_SECS:
static const char *h2_conf_set_session_extra_files(cmd_parms *parms,
void *arg, const char *value)
{
- h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
- apr_int64_t max = (int)apr_atoi64(value);
- if (max < 0) {
- return "value must be a non-negative number";
- }
- cfg->session_extra_files = (int)max;
+ /* deprecated, ignore */
(void)arg;
+ (void)value;
+ ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, parms->pool, /* NO LOGNO */
+ "H2SessionExtraFiles is obsolete and will be ignored");
return NULL;
}
AP_INIT_TAKE1("H2Direct", h2_conf_set_direct, NULL,
RSRC_CONF, "on to enable direct HTTP/2 mode"),
AP_INIT_TAKE1("H2SessionExtraFiles", h2_conf_set_session_extra_files, NULL,
- RSRC_CONF, "number of extra file a session might keep open"),
+ RSRC_CONF, "number of extra file a session might keep open (obsolete)"),
AP_INIT_TAKE1("H2TLSWarmUpSize", h2_conf_set_tls_warmup_size, NULL,
RSRC_CONF, "number of bytes on TLS connection before doing max writes"),
AP_INIT_TAKE1("H2TLSCoolDownSecs", h2_conf_set_tls_cooldown_secs, NULL,
H2_CONF_ALT_SVC_MAX_AGE,
H2_CONF_SER_HEADERS,
H2_CONF_DIRECT,
- H2_CONF_SESSION_FILES,
H2_CONF_MODERN_TLS_ONLY,
H2_CONF_UPGRADE,
H2_CONF_TLS_WARMUP_SIZE,
int serialize_headers; /* Use serialized HTTP/1.1 headers for
processing, better compatibility */
int h2_direct; /* if mod_h2 is active directly */
- int session_extra_files; /* # of extra files a session may keep open */
int modern_tls_only; /* Accept only modern TLS in HTTP/2 connections */
int h2_upgrade; /* Allow HTTP/1 upgrade to h2/h2c */
apr_int64_t tls_warmup_size; /* Amount of TLS data to send before going full write size */
{
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;
maxw = minw;
}
- /* 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);
+ "h2_workers: min=%d max=%d, mthrpchild=%d",
+ minw, maxw, max_threads_per_child);
+ workers = h2_workers_create(s, pool, minw, maxw);
idle_secs = h2_config_geti(config, H2_CONF_MAX_WORKER_IDLE_SECS);
h2_workers_set_max_idle_secs(workers, idle_secs);
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 = apr_psprintf(pool, "%ld-%d",
}
}
-static int can_beam_file(void *ctx, h2_bucket_beam *beam, apr_file_t *file)
+static int can_always_beam_file(void *ctx, h2_bucket_beam *beam, apr_file_t *file)
{
- h2_mplx *m = ctx;
- if (m->tx_handles_reserved > 0) {
- --m->tx_handles_reserved;
- ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, m->c,
- "h2_stream(%ld-%d,%s): beaming file, tx_avail %d",
- m->id, beam->id, beam->tag, m->tx_handles_reserved);
- return 1;
- }
- ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, m->c,
- "h2_stream(%ld-%d,%s): can_beam_file denied",
- m->id, beam->id, beam->tag);
- return 0;
-}
-
-static void check_tx_reservation(h2_mplx *m)
-{
- if (m->tx_handles_reserved <= 0) {
- m->tx_handles_reserved += h2_workers_tx_reserve(m->workers,
- H2MIN(m->tx_chunk_size, h2_ihash_count(m->streams)));
- }
-}
-
-static void check_tx_free(h2_mplx *m)
-{
- if (m->tx_handles_reserved > m->tx_chunk_size) {
- apr_size_t count = m->tx_handles_reserved - m->tx_chunk_size;
- m->tx_handles_reserved = m->tx_chunk_size;
- h2_workers_tx_free(m->workers, count);
- }
- else if (m->tx_handles_reserved && h2_ihash_empty(m->streams)) {
- h2_workers_tx_free(m->workers, m->tx_handles_reserved);
- m->tx_handles_reserved = 0;
- }
+ return 1;
}
static void stream_joined(h2_mplx *m, h2_stream *stream)
h2_ihash_remove(m->shold, stream->id);
h2_ihash_add(m->spurge, stream);
- if (stream->input) {
- m->tx_handles_reserved += h2_beam_get_files_beamed(stream->input);
- }
- m->tx_handles_reserved += h2_beam_get_files_beamed(stream->output);
}
static void stream_cleanup(h2_mplx *m, h2_stream *stream)
m->last_limit_change = m->last_idle_block = apr_time_now();
m->limit_change_interval = apr_time_from_msec(200);
- m->tx_handles_reserved = 0;
- m->tx_chunk_size = 4;
-
m->spare_slaves = apr_array_make(m->pool, 10, sizeof(conn_rec*));
m->ngn_shed = h2_ngn_shed_create(m->pool, m->c, m->max_streams,
while (!h2_ihash_iter(m->spurge, stream_destroy_iter, m)) {
/* repeat until empty */
}
- check_tx_free(m);
}
}
m->id, (int)h2_ihash_count(m->shold));
h2_ihash_iter(m->shold, unexpected_stream_iter, m);
}
- /*ap_assert(h2_ihash_empty(m->shold));*/
-
- /* 5. return any file resources allocated */
- check_tx_free(m);
leave_mutex(m, acquired);
{
apr_status_t status = APR_SUCCESS;
h2_stream *stream = h2_ihash_get(m->streams, stream_id);
- apr_size_t beamed_count;
if (!stream || !stream->task) {
return APR_ECONNABORTED;
h2_beam_on_consumed(stream->output, NULL, stream_output_consumed, stream);
h2_beam_on_produced(stream->output, output_produced, m);
- beamed_count = h2_beam_get_files_beamed(stream->output);
- if (m->tx_handles_reserved >= beamed_count) {
- m->tx_handles_reserved -= beamed_count;
- }
- else {
- m->tx_handles_reserved = 0;
- }
if (!stream->task->output.copy_files) {
- h2_beam_on_file_beam(stream->output, can_beam_file, m);
+ h2_beam_on_file_beam(stream->output, can_always_beam_file, m);
}
/* time to protect the beam against multi-threaded use */
/* we might see some file buckets in the output, see
* if we have enough handles reserved. */
- check_tx_reservation(m);
check_data_for(m, stream->id);
return status;
}
stream = h2_ihash_get(m->streams, sid);
if (stream) {
conn_rec *slave, **pslave;
- int new_conn = 0;
pslave = (conn_rec **)apr_array_pop(m->spare_slaves);
if (pslave) {
}
else {
slave = h2_slave_create(m->c, stream->id, m->pool);
- new_conn = 1;
}
slave->sbh = m->c->sbh;
m->c->keepalives++;
apr_table_setn(slave->notes, H2_TASK_ID_NOTE, stream->task->id);
- if (new_conn) {
- h2_slave_run_pre_connection(slave, ap_get_conn_socket(slave));
- }
+ h2_slave_run_pre_connection(slave, ap_get_conn_socket(slave));
+
if (sid > m->max_stream_started) {
m->max_stream_started = sid;
}
if (stream->input) {
h2_beam_on_consumed(stream->input, stream_input_ev,
stream_input_consumed, m);
- h2_beam_on_file_beam(stream->input, can_beam_file, m);
+ h2_beam_on_file_beam(stream->input, can_always_beam_file, m);
h2_beam_mutex_enable(stream->input);
}
apr_array_header_t *spare_slaves; /* spare slave connections */
struct h2_workers *workers;
- int tx_handles_reserved;
- int tx_chunk_size;
h2_mplx_consumed_cb *input_consumed;
void *input_consumed_ctx;
}
h2_workers *h2_workers_create(server_rec *s, apr_pool_t *server_pool,
- int min_workers, int max_workers,
- apr_size_t max_tx_handles)
+ int min_workers, int max_workers)
{
apr_status_t status;
h2_workers *workers;
workers->max_workers = max_workers;
workers->max_idle_secs = 10;
- workers->max_tx_handles = max_tx_handles;
- workers->spare_tx_handles = workers->max_tx_handles;
-
apr_threadattr_create(&workers->thread_attr, workers->pool);
if (ap_thread_stacksize != 0) {
apr_threadattr_stacksize_set(workers->thread_attr,
if (status == APR_SUCCESS) {
status = apr_thread_cond_create(&workers->mplx_added, workers->pool);
}
- if (status == APR_SUCCESS) {
- status = apr_thread_mutex_create(&workers->tx_lock,
- APR_THREAD_MUTEX_DEFAULT,
- workers->pool);
- }
if (status == APR_SUCCESS) {
status = h2_workers_start(workers);
}
apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m)
{
apr_status_t status = apr_thread_mutex_lock(workers->lock);
- if (status == APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_TRACE3, status, workers->s,
- "h2_workers: register mplx(%ld), idle=%d",
- m->id, workers->idle_workers);
- if (in_list(workers, m)) {
- status = APR_EAGAIN;
- }
- else {
- H2_MPLX_LIST_INSERT_TAIL(&workers->mplxs, m);
- ++workers->mplx_count;
- status = APR_SUCCESS;
- }
-
- if (workers->idle_workers > 0) {
- apr_thread_cond_signal(workers->mplx_added);
- }
- else if (status == APR_SUCCESS
- && workers->worker_count < workers->max_workers) {
- ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
- "h2_workers: got %d worker, adding 1",
- workers->worker_count);
- add_worker(workers);
- }
- apr_thread_mutex_unlock(workers->lock);
+ if (status != APR_SUCCESS) {
+ return status;
+ }
+
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, status, workers->s,
+ "h2_workers: register mplx(%ld), idle=%d",
+ m->id, workers->idle_workers);
+ if (in_list(workers, m)) {
+ status = APR_EAGAIN;
}
+ else {
+ H2_MPLX_LIST_INSERT_TAIL(&workers->mplxs, m);
+ ++workers->mplx_count;
+ status = APR_SUCCESS;
+ }
+
+ if (workers->idle_workers > 0) {
+ apr_thread_cond_signal(workers->mplx_added);
+ }
+ else if (status == APR_SUCCESS
+ && workers->worker_count < workers->max_workers) {
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
+ "h2_workers: got %d worker, adding 1",
+ workers->worker_count);
+ add_worker(workers);
+ }
+ apr_thread_mutex_unlock(workers->lock);
return status;
}
workers->max_idle_secs = idle_secs;
}
-apr_size_t h2_workers_tx_reserve(h2_workers *workers, apr_size_t count)
-{
- apr_status_t status = apr_thread_mutex_lock(workers->tx_lock);
- if (status == APR_SUCCESS) {
- count = H2MIN(workers->spare_tx_handles, count);
- workers->spare_tx_handles -= count;
- ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, workers->s,
- "h2_workers: reserved %d tx handles, %d/%d left",
- (int)count, (int)workers->spare_tx_handles,
- (int)workers->max_tx_handles);
- apr_thread_mutex_unlock(workers->tx_lock);
- return count;
- }
- return 0;
-}
-
-void h2_workers_tx_free(h2_workers *workers, apr_size_t count)
-{
- apr_status_t status = apr_thread_mutex_lock(workers->tx_lock);
- if (status == APR_SUCCESS) {
- workers->spare_tx_handles += count;
- ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, workers->s,
- "h2_workers: freed %d tx handles, %d/%d left",
- (int)count, (int)workers->spare_tx_handles,
- (int)workers->max_tx_handles);
- apr_thread_mutex_unlock(workers->tx_lock);
- }
-}
-
int idle_workers;
int max_idle_secs;
- apr_size_t max_tx_handles;
- apr_size_t spare_tx_handles;
-
unsigned int aborted : 1;
apr_threadattr_t *thread_attr;
struct apr_thread_mutex_t *lock;
struct apr_thread_cond_t *mplx_added;
-
- struct apr_thread_mutex_t *tx_lock;
};
* threads.
*/
h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pool,
- int min_size, int max_size,
- apr_size_t max_tx_handles);
+ int min_size, int max_size);
/**
* Registers a h2_mplx for task scheduling. If this h2_mplx runs