</usage>
</directivesynopsis>
- <directivesynopsis>
- <name>H2BufferWriteMax</name>
- <description>Maximum size of write on a HTTP/2 connection.</description>
- <syntax>H2BufferWriteMax <em>bytes</em></syntax>
- <default>H2BufferWriteMax 16384</default>
- <contextlist>
- <context>server config</context>
- <context>virtual host</context>
- </contextlist>
- <usage>
- <p>
- This directive sets maximum amount of data sent out in a single
- write on a http/2 connection. It only takes effect when
- <code>H2BufferOutput</code> is switched on.
- </p><p>
- This directive affects performance of underlying TLS transports. TLS
- transforms each write into an encrypted record. Clients need
- to receive all of the record in order to decrypt it. Larger sizes
- result in better server performance, shorter sizes can affect web
- page paint timings.
- </p><p>
- <code>BufferSize</code> should be a multiple of <code>H2BufferWriteMax</code>.
- <code>H2BufferWriteMax</code>, if larger than 16k, should be a multiple of 16k,
- since this is the TLS max record size. Be aware that there are TLS
- extensions to limit the record size to powers of 2 less than 16k.
- </p>
- <example><title>Example</title>
- <highlight language="config">
- H2BufferWriteMax 8000
- </highlight>
- </example>
- </usage>
- </directivesynopsis>
-
<directivesynopsis>
<name>H2SessionExtraFiles</name>
<description>Number of Extra File Handles</description>
NULL, /* no alt-svcs */
-1, /* alt-svc max age */
0, /* serialize headers */
- 1, /* hack mpm event */
1, /* h2 direct mode */
-1, /* buffer output, by default only for TLS */
64*1024, /* buffer size */
- 16*1024, /* out write max */
5, /* # session extra files */
};
conf->stream_max_mem_size = DEF_VAL;
conf->alt_svc_max_age = DEF_VAL;
conf->serialize_headers = DEF_VAL;
- conf->hack_mpm_event = DEF_VAL;
conf->h2_direct = DEF_VAL;
conf->buffer_output = DEF_VAL;
- conf->buffer_output = DEF_VAL;
conf->buffer_size = DEF_VAL;
- conf->write_max = DEF_VAL;
conf->session_extra_files = DEF_VAL;
return conf;
}
n->alt_svcs = add->alt_svcs? add->alt_svcs : base->alt_svcs;
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->hack_mpm_event = H2_CONFIG_GET(add, base, hack_mpm_event);
n->h2_direct = H2_CONFIG_GET(add, base, h2_direct);
n->buffer_output = H2_CONFIG_GET(add, base, buffer_output);
n->buffer_size = H2_CONFIG_GET(add, base, buffer_size);
- n->write_max = H2_CONFIG_GET(add, base, write_max);
n->session_extra_files = H2_CONFIG_GET(add, base, session_extra_files);
return n;
return H2_CONFIG_GET(conf, &defconf, alt_svc_max_age);
case H2_CONF_SER_HEADERS:
return H2_CONFIG_GET(conf, &defconf, serialize_headers);
- case H2_CONF_HACK_MPM_EVENT:
- return H2_CONFIG_GET(conf, &defconf, hack_mpm_event);
case H2_CONF_DIRECT:
return H2_CONFIG_GET(conf, &defconf, h2_direct);
case H2_CONF_BUFFER_OUTPUT:
return H2_CONFIG_GET(conf, &defconf, buffer_output);
case H2_CONF_BUFFER_SIZE:
return H2_CONFIG_GET(conf, &defconf, buffer_size);
- case H2_CONF_WRITE_MAX:
- return H2_CONFIG_GET(conf, &defconf, write_max);
case H2_CONF_SESSION_FILES:
return H2_CONFIG_GET(conf, &defconf, session_extra_files);
default:
return NULL;
}
-static const char *h2_conf_set_write_max(cmd_parms *parms,
- void *arg, const char *value)
-{
- h2_config *cfg = h2_config_sget(parms->server);
- apr_int64_t max = (int)apr_atoi64(value);
- if (max <= 0) {
- return "value must be a positive number";
- }
- else if (max > cfg->buffer_size) {
- return "value must be less than H2BufferSize";
- }
- cfg->write_max = (int)max;
- (void)arg;
- return NULL;
-}
-
static const char *h2_conf_set_session_extra_files(cmd_parms *parms,
void *arg, const char *value)
{
return "value must be On or Off";
}
-static const char *h2_conf_set_hack_mpm_event(cmd_parms *parms,
- void *arg, const char *value)
-{
- h2_config *cfg = h2_config_sget(parms->server);
- if (!strcasecmp(value, "On")) {
- cfg->hack_mpm_event = 1;
- return NULL;
- }
- else if (!strcasecmp(value, "Off")) {
- cfg->hack_mpm_event = 0;
- return NULL;
- }
-
- (void)arg;
- return "value must be On or Off";
-}
-
static const char *h2_conf_set_direct(cmd_parms *parms,
void *arg, const char *value)
{
RSRC_CONF, "set the maximum age (in seconds) that client can rely on alt-svc information"),
AP_INIT_TAKE1("H2SerializeHeaders", h2_conf_set_serialize_headers, NULL,
RSRC_CONF, "on to enable header serialization for compatibility"),
- AP_INIT_TAKE1("H2HackMpmEvent", h2_conf_set_hack_mpm_event, NULL,
- RSRC_CONF, "on to enable a hack that makes mpm_event working with mod_h2"),
AP_INIT_TAKE1("H2Direct", h2_conf_set_direct, NULL,
RSRC_CONF, "on to enable direct HTTP/2 mode"),
AP_INIT_TAKE1("H2BufferOutput", h2_conf_set_buffer_output, NULL,
RSRC_CONF, "on to enable output buffering, default for TLS"),
AP_INIT_TAKE1("H2BufferSize", h2_conf_set_buffer_size, NULL,
RSRC_CONF, "size of outgoing buffer in bytes"),
- AP_INIT_TAKE1("H2BufferWriteMax", h2_conf_set_write_max, NULL,
- RSRC_CONF, "maximum number of bytes in a outgoing write"),
AP_INIT_TAKE1("H2SessionExtraFiles", h2_conf_set_session_extra_files, NULL,
RSRC_CONF, "number of extra file a session might keep open"),
{ NULL, NULL, NULL, 0, 0, NULL }
H2_CONF_ALT_SVCS,
H2_CONF_ALT_SVC_MAX_AGE,
H2_CONF_SER_HEADERS,
- H2_CONF_HACK_MPM_EVENT,
H2_CONF_DIRECT,
H2_CONF_BUFFER_OUTPUT,
H2_CONF_BUFFER_SIZE,
- H2_CONF_WRITE_MAX,
H2_CONF_SESSION_FILES,
} h2_config_var_t;
int alt_svc_max_age; /* seconds clients can rely on alt-svc info*/
int serialize_headers; /* Use serialized HTTP/1.1 headers for
processing, better compatibility */
- int hack_mpm_event; /* If mpm_event is detected, perform a hack
- on stream connections to make it work */
int h2_direct; /* if mod_h2 is active directly */
int buffer_output; /* if output buffering shall be used */
int buffer_size; /* size of buffer for outgoing data */
- int write_max; /* max number of bytes for a write op */
int session_extra_files; /* # of extra files a session may keep open */
} h2_config;
/* all fine */
break;
case H2_MPM_EVENT:
- if (h2_config_geti(cfg, H2_CONF_HACK_MPM_EVENT)) {
- fix_event_conn(&env->c, master);
- }
+ fix_event_conn(&env->c, master);
break;
default:
/* fingers crossed */
/* all fine */
break;
case H2_MPM_EVENT:
- if (h2_config_geti(cfg, H2_CONF_HACK_MPM_EVENT)) {
- fix_event_conn(&env->c, master);
- }
+ fix_event_conn(&env->c, master);
break;
default:
/* fingers crossed */
#include "h2_h2.h"
#include "h2_util.h"
+#define WRITE_SIZE_INITIAL 1300
+#define WRITE_SIZE_MAX (16*1024)
+#define WRITE_SIZE_IDLE_USEC (1*APR_USEC_PER_SEC)
+#define WRITE_SIZE_THRESHOLD (1*1024*1024)
+
apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c)
{
h2_config *cfg = h2_config_get(c);
io->input = apr_brigade_create(c->pool, c->bucket_alloc);
io->output = apr_brigade_create(c->pool, c->bucket_alloc);
io->buflen = 0;
- io->max_write_size = h2_config_geti(cfg, H2_CONF_WRITE_MAX);
+ /* That is where we start with,
+ * see https://issues.apache.org/jira/browse/TS-2503 */
+ io->write_size = WRITE_SIZE_INITIAL;
+ io->last_write = 0;
io->buffer_output = h2_config_geti(cfg, H2_CONF_BUFFER_OUTPUT);
if (io->buffer_output < 0) {
{
h2_conn_io *io = (h2_conn_io*)ctx;
apr_status_t status;
+ apr_off_t bblen;
ap_update_child_status(io->connection->sbh, SERVER_BUSY_WRITE, NULL);
-
- status = ap_pass_brigade(io->connection->output_filters, bb);
- apr_brigade_cleanup(bb);
+ status = apr_brigade_length(bb, 1, &bblen);
+ if (status == APR_SUCCESS) {
+ status = ap_pass_brigade(io->connection->output_filters, bb);
+ if (status == APR_SUCCESS) {
+ io->bytes_written += (apr_size_t)bblen;
+ io->last_write = apr_time_now();
+ }
+ apr_brigade_cleanup(bb);
+ }
return status;
}
static apr_status_t bucketeer_buffer(h2_conn_io *io) {
const char *data = io->buffer;
apr_size_t remaining = io->buflen;
- int bcount = (int)(remaining / io->max_write_size);
apr_bucket *b;
- int i;
+ int bcount, i;
+
+ if (io->write_size > WRITE_SIZE_INITIAL
+ && (apr_time_now() - io->last_write) >= WRITE_SIZE_IDLE_USEC) {
+ /* 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,
+ "h2_conn_io(%ld): timeout write size reset to %ld",
+ (long)io->connection->id, (long)io->write_size);
+ }
+ else if (io->write_size < WRITE_SIZE_MAX
+ && io->bytes_written >= WRITE_SIZE_THRESHOLD) {
+ /* connection is hot, use max size */
+ io->write_size = WRITE_SIZE_MAX;
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, io->connection,
+ "h2_conn_io(%ld): threshold reached, write size now %ld",
+ (long)io->connection->id, (long)io->write_size);
+ }
+ bcount = (int)(remaining / io->write_size);
for (i = 0; i < bcount; ++i) {
- b = apr_bucket_transient_create(data, io->max_write_size,
+ b = apr_bucket_transient_create(data, io->write_size,
io->output->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(io->output, b);
- data += io->max_write_size;
- remaining -= io->max_write_size;
+ data += io->write_size;
+ remaining -= io->write_size;
}
if (remaining > 0) {
if (io->unflushed) {
apr_status_t status;
if (io->buflen > 0) {
- apr_bucket *b;
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, io->connection,
"h2_conn_io: flush, flushing %ld bytes", (long)io->buflen);
- b = apr_bucket_transient_create(io->buffer, io->buflen,
- io->output->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(io->output, b);
+ bucketeer_buffer(io);
io->buflen = 0;
}
/* Append flush.
apr_bucket_brigade *input;
apr_bucket_brigade *output;
int buffer_output;
- int max_write_size;
+ int write_size;
+ apr_time_t last_write;
+ apr_size_t bytes_written;
char *buffer;
apr_size_t buflen;