-*- coding: utf-8 -*-
Changes with Apache 2.5.0
- *) mod_http2: fix for missing score board updates on request count, fix for
- memory leak on slave connection reuse.
+ *) mod_http2: more efficient passing of response bodies with less contention
+ and file bucket forwarding. [Stefan Eissing]
- *) mod_http2: disabling PUSH when client sends GOAWAY.
-
*) mod_proxy_http2: using HTTP/2 flow control for backend streams by
observing data actually send out on the frontend h2 connection.
[Stefan Eissing]
return APR_SUCCESS;
}
-static int is_out_readable(h2_io *io, apr_off_t *plen, int *peos,
- apr_status_t *ps)
+apr_status_t h2_io_out_get_brigade(h2_io *io, apr_bucket_brigade *bb,
+ apr_off_t len)
{
if (io->rst_error) {
- *ps = APR_ECONNABORTED;
- return 0;
+ return APR_ECONNABORTED;
}
if (io->eos_out_read) {
- *plen = 0;
- *peos = 1;
- *ps = APR_SUCCESS;
- return 0;
+ return APR_EOF;
}
else if (!io->bbout) {
- *plen = 0;
- *peos = 0;
- *ps = APR_EAGAIN;
- return 0;
- }
- return 1;
-}
-
-apr_status_t h2_io_out_readx(h2_io *io,
- h2_io_data_cb *cb, void *ctx,
- apr_off_t *plen, int *peos)
-{
- apr_status_t status;
- if (!is_out_readable(io, plen, peos, &status)) {
- return status;
- }
- if (cb == NULL) {
- /* just checking length available */
- status = h2_util_bb_avail(io->bbout, plen, peos);
+ return APR_EAGAIN;
}
else {
- status = h2_util_bb_readx(io->bbout, cb, ctx, plen, peos);
- if (status == APR_SUCCESS) {
- io->eos_out_read = *peos;
- io->output_consumed += *plen;
+ apr_status_t status;
+ apr_off_t pre_len, post_len;
+ /* Allow file handles pass through without limits. If they
+ * already have the lifetime of this stream, we might as well
+ * pass them on to the master connection */
+ apr_size_t files = INT_MAX;
+
+ apr_brigade_length(bb, 0, &pre_len);
+ status = h2_util_move(bb, io->bbout, len, &files, "h2_io_read_to");
+ if (status == APR_SUCCESS && io->eos_out
+ && APR_BRIGADE_EMPTY(io->bbout)) {
+ io->eos_out_read = 1;
}
- }
- return status;
-}
-
-apr_status_t h2_io_out_read_to(h2_io *io, apr_bucket_brigade *bb,
- apr_off_t *plen, int *peos)
-{
- apr_status_t status;
- if (!is_out_readable(io, plen, peos, &status)) {
+ apr_brigade_length(bb, 0, &post_len);
+ io->output_consumed += (post_len - pre_len);
return status;
}
- status = h2_util_move(bb, io->bbout, *plen, NULL, "h2_io_read_to");
- if (status == APR_SUCCESS && io->eos_out && APR_BRIGADE_EMPTY(io->bbout)) {
- io->eos_out_read = *peos = 1;
- }
- io->output_consumed += *plen;
- return status;
}
static void process_trailers(h2_io *io, apr_table_t *trailers)
* @param plen the requested max len, set to amount of data on return
* @param peos != 0 iff the end of stream has been reached
*/
-apr_status_t h2_io_out_readx(h2_io *io,
- h2_io_data_cb *cb, void *ctx,
- apr_off_t *plen, int *peos);
-
-apr_status_t h2_io_out_read_to(h2_io *io,
- apr_bucket_brigade *bb,
- apr_off_t *plen, int *peos);
+apr_status_t h2_io_out_get_brigade(h2_io *io,
+ apr_bucket_brigade *bb,
+ apr_off_t len);
apr_status_t h2_io_out_write(h2_io *io, apr_bucket_brigade *bb,
apr_size_t maxlen, apr_table_t *trailers,
return status;
}
-apr_status_t h2_mplx_out_readx(h2_mplx *m, int stream_id,
- h2_io_data_cb *cb, void *ctx,
- apr_off_t *plen, int *peos,
- apr_table_t **ptrailers)
-{
- apr_status_t status;
- int acquired;
-
- AP_DEBUG_ASSERT(m);
- if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
- h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
- if (io && !io->orphaned) {
- H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_pre");
-
- status = h2_io_out_readx(io, cb, ctx, plen, peos);
- H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_readx_post");
- if (status == APR_SUCCESS && cb) {
- h2_io_signal(io, H2_IO_WRITE);
- }
- }
- else {
- status = APR_ECONNABORTED;
- }
-
- *ptrailers = (*peos && io->response)? io->response->trailers : NULL;
- leave_mutex(m, acquired);
- }
- return status;
-}
-
-apr_status_t h2_mplx_out_read_to(h2_mplx *m, int stream_id,
- apr_bucket_brigade *bb,
- apr_off_t *plen, int *peos,
- apr_table_t **ptrailers)
+apr_status_t h2_mplx_out_get_brigade(h2_mplx *m, int stream_id,
+ apr_bucket_brigade *bb,
+ apr_off_t len, apr_table_t **ptrailers)
{
apr_status_t status;
int acquired;
if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
h2_io *io = h2_io_set_get(m->stream_ios, stream_id);
if (io && !io->orphaned) {
- H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_read_to_pre");
+ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_get_brigade_pre");
- status = h2_io_out_read_to(io, bb, plen, peos);
+ status = h2_io_out_get_brigade(io, bb, len);
- H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_read_to_post");
+ H2_MPLX_IO_OUT(APLOG_TRACE2, m, io, "h2_mplx_out_get_brigade_post");
if (status == APR_SUCCESS) {
h2_io_signal(io, H2_IO_WRITE);
}
else {
status = APR_ECONNABORTED;
}
- *ptrailers = (*peos && io->response)? io->response->trailers : NULL;
+ *ptrailers = io->response? io->response->trailers : NULL;
leave_mutex(m, acquired);
}
return status;
struct h2_stream *h2_mplx_next_submit(h2_mplx *m,
struct h2_ihash_t *streams);
-/**
- * Reads output data from the given stream. Will never block, but
- * return APR_EAGAIN until data arrives or the stream is closed.
- */
-apr_status_t h2_mplx_out_readx(h2_mplx *mplx, int stream_id,
- h2_io_data_cb *cb, void *ctx,
- apr_off_t *plen, int *peos,
- apr_table_t **ptrailers);
-
/**
* Reads output data into the given brigade. Will never block, but
* return APR_EAGAIN until data arrives or the stream is closed.
*/
-apr_status_t h2_mplx_out_read_to(h2_mplx *mplx, int stream_id,
- apr_bucket_brigade *bb,
- apr_off_t *plen, int *peos,
- apr_table_t **ptrailers);
+apr_status_t h2_mplx_out_get_brigade(h2_mplx *mplx, int stream_id,
+ apr_bucket_brigade *bb,
+ apr_off_t len, apr_table_t **ptrailers);
/**
* Opens the output for the given stream with the specified response.
unsigned char padlen;
int eos;
h2_stream *stream;
+ apr_bucket *b;
(void)ngh2;
(void)source;
}
}
else {
- apr_bucket *b;
- char *header = apr_pcalloc(stream->pool, 10);
- memcpy(header, (const char *)framehd, 9);
- if (padlen) {
- header[9] = (char)padlen;
+ status = h2_conn_io_write(&session->io, (const char *)framehd, 9);
+ if (padlen && status == APR_SUCCESS) {
+ status = h2_conn_io_write(&session->io, (const char *)&padlen, 1);
}
- b = apr_bucket_pool_create(header, padlen? 10 : 9,
- stream->pool, session->c->bucket_alloc);
- status = h2_conn_io_writeb(&session->io, b);
-
if (status == APR_SUCCESS) {
apr_off_t len = length;
status = h2_stream_read_to(stream, session->io.output, &len, &eos);
typedef struct h2_sos_mplx {
h2_mplx *m;
apr_bucket_brigade *bb;
+ apr_bucket_brigade *tmp;
apr_table_t *trailers;
+ apr_off_t buffer_size;
} h2_sos_mplx;
#define H2_SOS_MPLX_OUT(lvl,msos,msg) \
} while(0)
-static apr_status_t h2_sos_mplx_read_to(h2_sos *sos, apr_bucket_brigade *bb,
- apr_off_t *plen, int *peos)
+static apr_status_t mplx_transfer(h2_sos_mplx *msos, int stream_id,
+ apr_pool_t *pool)
{
- h2_sos_mplx *msos = sos->ctx;
- apr_status_t status = APR_SUCCESS;
+ apr_status_t status;
apr_table_t *trailers = NULL;
-
- H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx read_to_pre");
- if (APR_BRIGADE_EMPTY(msos->bb)) {
- apr_off_t tlen = *plen;
- int eos;
- status = h2_mplx_out_read_to(msos->m, sos->stream->id,
- msos->bb, &tlen, &eos, &trailers);
+ if (!msos->tmp) {
+ msos->tmp = apr_brigade_create(msos->bb->p, msos->bb->bucket_alloc);
}
-
- if (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(msos->bb)) {
- status = h2_transfer_brigade(bb, msos->bb, sos->stream->pool,
- plen, peos);
- }
- else {
- *plen = 0;
- *peos = 0;
+ status = h2_mplx_out_get_brigade(msos->m, stream_id, msos->tmp,
+ msos->buffer_size-1, &trailers);
+ if (status == APR_SUCCESS) {
+ status = h2_transfer_brigade(msos->bb, msos->tmp, pool);
}
-
if (trailers) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, msos->m->c,
- "h2_stream(%ld-%d): read_to, saving trailers",
- msos->m->id, sos->stream->id);
msos->trailers = trailers;
}
-
+ return status;
+}
+
+static apr_status_t h2_sos_mplx_read_to(h2_sos *sos, apr_bucket_brigade *bb,
+ apr_off_t *plen, int *peos)
+{
+ h2_sos_mplx *msos = sos->ctx;
+ apr_status_t status;
+
+ status = h2_append_brigade(bb, msos->bb, plen, peos);
if (status == APR_SUCCESS && !*peos && !*plen) {
status = APR_EAGAIN;
}
- H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx read_to_post");
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, msos->m->c,
"h2_stream(%ld-%d): read_to, len=%ld eos=%d",
msos->m->id, sos->stream->id, (long)*plen, *peos);
return status;
}
-static apr_status_t h2_sos_mplx_prep_read(h2_sos *sos, apr_off_t *plen, int *peos)
+static apr_status_t h2_sos_mplx_readx(h2_sos *sos, h2_io_data_cb *cb, void *ctx,
+ apr_off_t *plen, int *peos)
{
h2_sos_mplx *msos = sos->ctx;
apr_status_t status = APR_SUCCESS;
- const char *src;
- apr_table_t *trailers = NULL;
- int test_read = (*plen == 0);
- H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx prep_read_pre");
- if (!APR_BRIGADE_EMPTY(msos->bb)) {
- src = "stream";
- status = h2_util_bb_avail(msos->bb, plen, peos);
- if (!test_read && status == APR_SUCCESS && !*peos && !*plen) {
- apr_brigade_cleanup(msos->bb);
- return h2_sos_mplx_prep_read(sos, plen, peos);
- }
- }
- else {
- src = "mplx";
- status = h2_mplx_out_readx(msos->m, sos->stream->id,
- NULL, NULL, plen, peos, &trailers);
- if (trailers) {
- msos->trailers = trailers;
- }
- }
-
- if (!test_read && status == APR_SUCCESS && !*peos && !*plen) {
+ status = h2_util_bb_readx(msos->bb, cb, ctx, plen, peos);
+ if (status == APR_SUCCESS && !*peos && !*plen) {
status = APR_EAGAIN;
}
-
- H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx prep_read_post");
ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, msos->m->c,
- "h2_stream(%ld-%d): prep_read %s, len=%ld eos=%d, trailers=%s",
- msos->m->id, sos->stream->id, src, (long)*plen, *peos,
- msos->trailers? "yes" : "no");
+ "h2_stream(%ld-%d): readx, len=%ld eos=%d",
+ msos->m->id, sos->stream->id, (long)*plen, *peos);
return status;
}
-static apr_status_t h2_sos_mplx_readx(h2_sos *sos, h2_io_data_cb *cb, void *ctx,
- apr_off_t *plen, int *peos)
+static apr_status_t h2_sos_mplx_prep_read(h2_sos *sos, apr_off_t *plen, int *peos)
{
h2_sos_mplx *msos = sos->ctx;
apr_status_t status = APR_SUCCESS;
- apr_table_t *trailers = NULL;
- const char *src;
- H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx readx_pre");
- *peos = 0;
- if (!APR_BRIGADE_EMPTY(msos->bb)) {
- apr_off_t origlen = *plen;
-
- src = "stream";
- status = h2_util_bb_readx(msos->bb, cb, ctx, plen, peos);
- if (status == APR_SUCCESS && !*peos && !*plen) {
- apr_brigade_cleanup(msos->bb);
- *plen = origlen;
- return h2_sos_mplx_readx(sos, cb, ctx, plen, peos);
- }
- }
- else {
- src = "mplx";
- status = h2_mplx_out_readx(msos->m, sos->stream->id,
- cb, ctx, plen, peos, &trailers);
- }
+ H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx prep_read_pre");
- if (trailers) {
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, msos->m->c,
- "h2_stream(%ld-%d): readx, saving trailers",
- msos->m->id, sos->stream->id);
- msos->trailers = trailers;
+ if (APR_BRIGADE_EMPTY(msos->bb)) {
+ status = mplx_transfer(msos, sos->stream->id, sos->stream->pool);
}
+ status = h2_util_bb_avail(msos->bb, plen, peos);
+ H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx prep_read_post");
+ ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, msos->m->c,
+ "h2_stream(%ld-%d): prep_read, len=%ld eos=%d, trailers=%s",
+ msos->m->id, sos->stream->id, (long)*plen, *peos,
+ msos->trailers? "yes" : "no");
if (status == APR_SUCCESS && !*peos && !*plen) {
status = APR_EAGAIN;
}
- H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_stream readx_post");
- ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, msos->m->c,
- "h2_stream(%ld-%d): readx %s, len=%ld eos=%d",
- msos->m->id, sos->stream->id, src, (long)*plen, *peos);
-
return status;
}
apr_status_t status = APR_SUCCESS;
if (bb && !APR_BRIGADE_EMPTY(bb)) {
- apr_size_t move_all = INT_MAX;
- /* we can move file handles from h2_mplx into this h2_stream as many
- * as we want, since the lifetimes are the same and we are not freeing
- * the ones in h2_mplx->io before this stream is done. */
H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx set_response_pre");
- status = h2_util_move(msos->bb, bb, 16 * 1024, &move_all,
- "h2_stream_set_response");
+ status = mplx_transfer(msos, sos->stream->id, sos->stream->pool);
H2_SOS_MPLX_OUT(APLOG_TRACE2, msos, "h2_sos_mplx set_response_post");
}
return status;
msos = apr_pcalloc(stream->pool, sizeof(*msos));
msos->m = stream->session->mplx;
msos->bb = apr_brigade_create(stream->pool, msos->m->c->bucket_alloc);
-
+ msos->buffer_size = 32 * 1024;
+
sos = apr_pcalloc(stream->pool, sizeof(*sos));
sos->stream = stream;
sos->response = response;
}
-apr_status_t h2_transfer_brigade(apr_bucket_brigade *to,
+apr_status_t h2_ltransfer_brigade(apr_bucket_brigade *to,
apr_bucket_brigade *from,
apr_pool_t *p,
apr_off_t *plen,
return APR_SUCCESS;
}
+apr_status_t h2_transfer_brigade(apr_bucket_brigade *to,
+ apr_bucket_brigade *from,
+ apr_pool_t *p)
+{
+ apr_bucket *e;
+ apr_status_t rv;
+
+ while (!APR_BRIGADE_EMPTY(from)) {
+ e = APR_BRIGADE_FIRST(from);
+
+ rv = apr_bucket_setaside(e, p);
+
+ /* If the bucket type does not implement setaside, then
+ * (hopefully) morph it into a bucket type which does, and set
+ * *that* aside... */
+ if (rv == APR_ENOTIMPL) {
+ const char *s;
+ apr_size_t n;
+
+ rv = apr_bucket_read(e, &s, &n, APR_BLOCK_READ);
+ if (rv == APR_SUCCESS) {
+ rv = apr_bucket_setaside(e, p);
+ }
+ }
+
+ if (rv != APR_SUCCESS) {
+ /* Return an error but still save the brigade if
+ * ->setaside() is really not implemented. */
+ if (rv != APR_ENOTIMPL) {
+ return rv;
+ }
+ }
+
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(to, e);
+ }
+ return APR_SUCCESS;
+}
+
+apr_status_t h2_append_brigade(apr_bucket_brigade *to,
+ apr_bucket_brigade *from,
+ apr_off_t *plen,
+ int *peos)
+{
+ apr_bucket *e;
+ apr_off_t len = 0, remain = *plen;
+ apr_status_t rv;
+
+ *peos = 0;
+
+ while (!APR_BRIGADE_EMPTY(from)) {
+ e = APR_BRIGADE_FIRST(from);
+
+ if (APR_BUCKET_IS_METADATA(e)) {
+ if (APR_BUCKET_IS_EOS(e)) {
+ *peos = 1;
+ }
+ }
+ else {
+ if (remain > 0 && e->length == ((apr_size_t)-1)) {
+ const char *ign;
+ apr_size_t ilen;
+ rv = apr_bucket_read(e, &ign, &ilen, APR_BLOCK_READ);
+ if (rv != APR_SUCCESS) {
+ return rv;
+ }
+ }
+
+ if (remain < e->length) {
+ if (remain <= 0) {
+ return APR_SUCCESS;
+ }
+ apr_bucket_split(e, remain);
+ }
+ }
+
+ APR_BUCKET_REMOVE(e);
+ APR_BRIGADE_INSERT_TAIL(to, e);
+ len += e->length;
+ remain -= e->length;
+ }
+
+ *plen = len;
+ return APR_SUCCESS;
+}
+
apr_off_t h2_brigade_mem_size(apr_bucket_brigade *bb)
{
apr_bucket *b;
/**
* Transfer buckets from one brigade to another with a limit on the
- * maximum amount of bytes transfered.
+ * maximum amount of bytes transfered. Sets aside the buckets to
+ * pool p.
* @param to brigade to transfer buckets to
* @param from brigades to remove buckets from
* @param p pool that buckets should be setaside to
* @param plen maximum bytes to transfer, actual bytes transferred
* @param peos if an EOS bucket was transferred
*/
+apr_status_t h2_ltransfer_brigade(apr_bucket_brigade *to,
+ apr_bucket_brigade *from,
+ apr_pool_t *p,
+ apr_off_t *plen,
+ int *peos);
+
+/**
+ * Transfer all buckets from one brigade to another. Sets aside the buckets to
+ * pool p.
+ * @param to brigade to transfer buckets to
+ * @param from brigades to remove buckets from
+ * @param p pool that buckets should be setaside to
+ */
apr_status_t h2_transfer_brigade(apr_bucket_brigade *to,
apr_bucket_brigade *from,
- apr_pool_t *p,
- apr_off_t *plen,
- int *peos);
+ apr_pool_t *p);
+
+/**
+ * Transfer buckets from one brigade to another with a limit on the
+ * maximum amount of bytes transfered. Does no setaside magic, lifetime
+ * of brigades must fit.
+ * @param to brigade to transfer buckets to
+ * @param from brigades to remove buckets from
+ * @param plen maximum bytes to transfer, actual bytes transferred
+ * @param peos if an EOS bucket was transferred
+ */
+apr_status_t h2_append_brigade(apr_bucket_brigade *to,
+ apr_bucket_brigade *from,
+ apr_off_t *plen,
+ int *peos);
/**
* Get an approximnation of the memory footprint of the given
* @macro
* Version number of the http2 module as c string
*/
-#define MOD_HTTP2_VERSION "1.4.5-DEV"
+#define MOD_HTTP2_VERSION "1.4.6-DEV"
/**
* @macro
* 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 0x010405
+#define MOD_HTTP2_VERSION_NUM 0x010406
#endif /* mod_h2_h2_version_h */