On Thu, Jan 23, 2020 at 8:23 AM Pluem, Ruediger, Vodafone Group
<[email protected]> wrote:
> >
> > https://github.com/apache/httpd/pull/88
>
> Anyone had a look?
If I understand the patch correctly, ap_request_core_filter() would
let everything through on EOR, regardless of potential morphing
buckets set aside?
So if ap_request_core_filter() yields because of network contention,
setting aside all the remaining buckets (say including a morphing
one), and then ap_process_request_after_handler() sends the EOR (the
handler has finished sending everything on its side), it seems that
ap_request_core_filter() won't play its role anymore?
This plus the fact that ap_request_core_filter() filters "stack up"
for each request, so the oldest request's filter may setaside data for
newer ones, with its own (wrong) request pool.
How about we use a single ap_request_core_filter() for all the
requests, at AP_FTYPE_CONNECTION level, and have it "detect" requests
based on SOR/EOR ranges, where the SOR (Start Of Request) bucket is to
be inserted by the AP_FTYPE_PROTOCOL module (e.g.
ap_http_header_filter for http) before sending any data pertaining to
its request?
It tried that in the attached patch, it passes the test framework and
Joe's reproducer.
Thoughts?
Regards,
Yann.
Index: server/core.c
===================================================================
--- server/core.c (revision 1875498)
+++ server/core.c (working copy)
@@ -5417,6 +5478,24 @@ static int core_create_req(request_rec *r)
return OK;
}
+static int core_create_request(request_rec *r)
+{
+ int rc = core_create_req(r);
+
+ if (rc == OK && !r->main && !r->prev) {
+ ap_filter_t *f = NULL;
+ conn_rec *c = r->connection;
+ apr_pool_userdata_get((void **)&f, "req_core_filter", c->pool);
+ if (f == NULL) {
+ f = ap_add_output_filter_handle(ap_request_core_filter_handle,
+ NULL, NULL, c);
+ apr_pool_userdata_setn(f, "req_core_filter", NULL, c->pool);
+ }
+ }
+
+ return rc;
+}
+
static int core_create_proxy_req(request_rec *r, request_rec *pr)
{
return core_create_req(pr);
@@ -5885,7 +5964,7 @@ static void register_hooks(apr_pool_t *p)
/* FIXME: I suspect we can eliminate the need for these do_nothings - Ben */
ap_hook_type_checker(do_nothing,NULL,NULL,APR_HOOK_REALLY_LAST);
ap_hook_fixups(core_override_type,NULL,NULL,APR_HOOK_REALLY_FIRST);
- ap_hook_create_request(core_create_req, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_create_request(core_create_request, NULL, NULL, APR_HOOK_MIDDLE);
APR_OPTIONAL_HOOK(proxy, create_req, core_create_proxy_req, NULL, NULL,
APR_HOOK_MIDDLE);
ap_hook_pre_mpm(ap_create_scoreboard, NULL, NULL, APR_HOOK_MIDDLE);
@@ -5918,7 +5997,7 @@ static void register_hooks(apr_pool_t *p)
NULL, AP_FTYPE_NETWORK);
ap_request_core_filter_handle =
ap_register_output_filter("REQ_CORE", ap_request_core_filter,
- NULL, AP_FTYPE_CONNECTION - 1);
+ NULL, AP_FTYPE_CONNECTION);
ap_subreq_core_filter_handle =
ap_register_output_filter("SUBREQ_CORE", ap_sub_req_output_filter,
NULL, AP_FTYPE_CONTENT_SET);
Index: modules/http/http_core.c
===================================================================
--- modules/http/http_core.c (revision 1875498)
+++ modules/http/http_core.c (working copy)
@@ -268,8 +268,6 @@ static int http_create_request(request_rec *r)
NULL, r, r->connection);
ap_add_output_filter_handle(ap_http_outerror_filter_handle,
NULL, r, r->connection);
- ap_add_output_filter_handle(ap_request_core_filter_handle,
- NULL, r, r->connection);
}
return OK;
Index: modules/http/http_request.c
===================================================================
--- modules/http/http_request.c (revision 1875498)
+++ modules/http/http_request.c (working copy)
@@ -350,7 +350,6 @@ AP_DECLARE(void) ap_process_request_after_handler(
apr_bucket_brigade *bb;
apr_bucket *b;
conn_rec *c = r->connection;
- ap_filter_t *f;
bb = ap_acquire_brigade(c);
@@ -371,15 +370,11 @@ AP_DECLARE(void) ap_process_request_after_handler(
/* All the request filters should have bailed out on EOS, and in any
* case they shouldn't have to handle this EOR which will destroy the
- * request underneath them. So go straight to the core request filter
- * which (if any) will take care of the setaside buckets.
+ * request underneath them. So go straight to the connection filters,
+ * the first of which being ap_request_core_filter() to take care of
+ * request level setaside buckets.
*/
- for (f = r->output_filters; f; f = f->next) {
- if (f->frec == ap_request_core_filter_handle) {
- break;
- }
- }
- ap_pass_brigade(f ? f : c->output_filters, bb);
+ ap_pass_brigade(c->output_filters, bb);
/* The EOR bucket has either been handled by an output filter (eg.
* deleted or moved to a buffered_bb => no more in bb), or an error
Index: modules/http/http_filters.c
===================================================================
--- modules/http/http_filters.c (revision 1875498)
+++ modules/http/http_filters.c (working copy)
@@ -1286,6 +1286,10 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_heade
*/
if (AP_BUCKET_IS_EOC(e)) {
ap_remove_output_filter(f);
+ if (!ctx->headers_sent) {
+ e = ap_bucket_sor_create(c->bucket_alloc, r);
+ APR_BRIGADE_INSERT_HEAD(b, e);
+ }
return ap_pass_brigade(f->next, b);
}
}
@@ -1452,6 +1456,8 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_http_heade
terminate_header(b2);
+ e = ap_bucket_sor_create(c->bucket_alloc, r);
+ APR_BRIGADE_INSERT_HEAD(b2, e);
if (header_only) {
e = APR_BRIGADE_LAST(b);
if (e != APR_BRIGADE_SENTINEL(b) && APR_BUCKET_IS_EOS(e)) {
Index: server/request.c
===================================================================
--- server/request.c (revision 1875498)
+++ server/request.c (working copy)
@@ -2058,17 +2058,23 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_sub_req_ou
return APR_SUCCESS;
}
+struct request_core_filter_ctx {
+ request_rec *r;
+ apr_bucket_brigade *bb;
+ apr_bucket_brigade *tmp_bb;
+};
+
AP_CORE_DECLARE_NONSTD(apr_status_t) ap_request_core_filter(ap_filter_t *f,
apr_bucket_brigade *bb)
{
apr_status_t status = APR_SUCCESS;
+ struct request_core_filter_ctx *ctx = f->ctx;
apr_read_type_e block = APR_NONBLOCK_READ;
- conn_rec *c = f->r->connection;
apr_bucket *flush_upto = NULL;
- apr_bucket_brigade *tmp_bb;
apr_size_t tmp_bb_len = 0;
+ apr_bucket *bucket, *next;
core_server_config *conf;
- int seen_eor = 0;
+ apr_status_t rv;
/*
* Handle the AsyncFilter directive. We limit the filters that are
@@ -2079,99 +2085,145 @@ AP_CORE_DECLARE_NONSTD(apr_status_t) ap_request_co
return ap_pass_brigade(f->next, bb);
}
- conf = ap_get_core_module_config(f->r->server->module_config);
+ if (ctx == NULL) {
+ ctx = apr_pcalloc(f->c->pool, sizeof(*ctx));
+ ctx->bb = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
+ ctx->tmp_bb = apr_brigade_create(f->c->pool, f->c->bucket_alloc);
+ f->ctx = ctx;
+ }
- /* Reinstate any buffered content */
- ap_filter_reinstate_brigade(f, bb, &flush_upto);
-
- /* After EOR is passed downstream, anything pooled on the request may
- * be destroyed (including bb!), but not tmp_bb which is acquired from
- * c->pool (and released after the below loop).
+ /* This filter tracks the ranges in between SOR:EOR buckets, so to use the
+ * relevant request pool for potential setasides (e.g. network congestion).
+ *
+ * Let f->r track the request for the current SOR:EOR range being walked,
+ * and ctx->r track the request at the start of ctx->bb. When it's time to
+ * send the/some buckets from ctx->bb out, ctx->r is set to f->r (i.e. the
+ * current request for the first remaining bucket), and the next call can
+ * start from there (anything not sent is pending to the next call).
+ *
+ * Outside SOR:EOR ranges, f->r is NULL thus potential setasides happen
+ * with no request pool; the buckets are assumed to be non-morphing and
+ * have the lifetime of the connection, supposedly metadata only.
*/
- tmp_bb = ap_acquire_brigade(f->c);
+ f->r = ctx->r;
+ conf = f->r ? ap_get_core_module_config(f->r->server->module_config)
+ : ap_get_core_module_config(f->c->base_server->module_config);
- /* Don't touch *bb after seen_eor */
- while (status == APR_SUCCESS && !seen_eor && !APR_BRIGADE_EMPTY(bb)) {
- apr_bucket *bucket = APR_BRIGADE_FIRST(bb);
- int do_pass = 0;
+ APR_BRIGADE_CONCAT(ctx->bb, bb);
+ ap_filter_reinstate_brigade(f, ctx->bb, &flush_upto);
- if (AP_BUCKET_IS_EOR(bucket)) {
- /* pass out everything and never come back again,
- * r is destroyed with this bucket!
+ while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(ctx->bb)) {
+ int do_pass = 0, do_morph;
+
+ bucket = APR_BRIGADE_FIRST(ctx->bb);
+ do_morph = (f->r && bucket->length == (apr_size_t)-1);
+
+ /* if the core has set aside data, back off and try later */
+ if (!flush_upto) {
+ if (ap_filter_should_yield(f->next)) {
+ break;
+ }
+ }
+ else if (flush_upto == bucket) {
+ /* If the above ap_filter_reinstate_brigade() wanted us to pass a
+ * morphing bucket because f->r was unset at that time, just ignore
+ * it here if we now know the current request. Below _read() will
+ * morph the bucket so the real data length will be accounted for
+ * in the max threshold anyway.
*/
- APR_BRIGADE_CONCAT(tmp_bb, bb);
- ap_remove_output_filter(f);
- seen_eor = 1;
+ do_pass = !do_morph;
+ flush_upto = NULL;
}
- else {
- /* if the core has set aside data, back off and try later */
- if (!flush_upto) {
- if (ap_filter_should_yield(f->next)) {
- break;
- }
- }
- else if (flush_upto == bucket) {
- flush_upto = NULL;
- }
- /* have we found a morphing bucket? if so, force it to morph into
- * something safe to pass down to the connection filters without
- * needing to be set aside.
+ if (AP_BUCKET_IS_SOR(bucket)) {
+ f->r = ap_bucket_sor_get_r(bucket);
+ conf = ap_get_core_module_config(f->r->server->module_config);
+ }
+ else if (AP_BUCKET_IS_EOR(bucket)) {
+ f->r = NULL;
+ conf = ap_get_core_module_config(f->c->base_server->module_config);
+ }
+ else if (do_morph) {
+ const char *data;
+ apr_size_t size;
+
+ /* Found a morphing bucket, force it to morph into something safe
+ * to pass down to the connection filters without needing to be
+ * set aside.
*/
- if (bucket->length == (apr_size_t)-1) {
- const char *data;
- apr_size_t size;
-
- status = apr_bucket_read(bucket, &data, &size, block);
- if (status != APR_SUCCESS) {
- if (!APR_STATUS_IS_EAGAIN(status)
- || block != APR_NONBLOCK_READ) {
- break;
- }
- /* Flush everything so far and retry in blocking mode */
- bucket = apr_bucket_flush_create(c->bucket_alloc);
+ rv = apr_bucket_read(bucket, &data, &size, block);
+ if (rv != APR_SUCCESS) {
+ if (APR_STATUS_IS_EAGAIN(rv) && block == APR_NONBLOCK_READ) {
+ /* Retry in blocking mode */
block = APR_BLOCK_READ;
}
else {
- tmp_bb_len += size;
- block = APR_NONBLOCK_READ;
+ /* Last loop, report failure still */
+ status = rv;
}
+ /* In any case, flush everything so far */
+ bucket = apr_bucket_flush_create(f->c->bucket_alloc);
+ do_pass = 1;
}
else {
- tmp_bb_len += bucket->length;
+ block = APR_NONBLOCK_READ;
}
+ }
- /* move the bucket to tmp_bb and check whether it exhausts bb or
- * brings tmp_bb above the limit; in both cases it's time to pass
- * everything down the chain.
- */
- APR_BUCKET_REMOVE(bucket);
- APR_BRIGADE_INSERT_TAIL(tmp_bb, bucket);
- if (APR_BRIGADE_EMPTY(bb)
- || APR_BUCKET_IS_FLUSH(bucket)
- || tmp_bb_len >= conf->flush_max_threshold) {
- do_pass = 1;
+ /* Move the bucket to ctx->tmp_bb and see if it empties ctx->bb, or if
+ * ctx->tmp_bb reaches flush threshold; in both cases it's time to pass
+ * everything down the chain.
+ */
+ APR_BUCKET_REMOVE(bucket);
+ APR_BRIGADE_INSERT_TAIL(ctx->tmp_bb, bucket);
+ if (do_pass
+ || APR_BRIGADE_EMPTY(ctx->bb)
+ || (bucket->length == (apr_size_t)-1)
+ || (tmp_bb_len += bucket->length) >=
+ conf->flush_max_threshold) {
+ rv = ap_pass_brigade(f->next, ctx->tmp_bb);
+ apr_brigade_cleanup(ctx->tmp_bb);
+ if (status == APR_SUCCESS) {
+ status = rv;
}
- }
-
- if (do_pass || seen_eor) {
- status = ap_pass_brigade(f->next, tmp_bb);
- apr_brigade_cleanup(tmp_bb);
tmp_bb_len = 0;
+ ctx->r = f->r;
}
}
- /* Don't touch *bb after seen_eor */
- if (!seen_eor) {
- apr_status_t rv;
- APR_BRIGADE_PREPEND(bb, tmp_bb);
- rv = ap_filter_setaside_brigade(f, bb);
- if (status == APR_SUCCESS) {
- status = rv;
+ /* Setaside remaining buckets left in ctx->[tmp_]bb (yield or error), but
+ * do that per request, according to the SOR:EOR range(s). First reset f->r
+ * to the request at the beginning of ctx->bb since we start from there.
+ */
+ f->r = ctx->r;
+ APR_BRIGADE_PREPEND(ctx->bb, ctx->tmp_bb);
+ for (bucket = APR_BRIGADE_FIRST(ctx->bb);
+ bucket != APR_BRIGADE_SENTINEL(ctx->bb);
+ bucket = next) {
+ next = APR_BUCKET_NEXT(bucket);
+
+ if (AP_BUCKET_IS_SOR(bucket)) {
+ f->r = ap_bucket_sor_get_r(bucket);
}
+ else if (AP_BUCKET_IS_EOR(bucket)
+ || next == APR_BRIGADE_SENTINEL(ctx->bb)) {
+ apr_brigade_split_ex(ctx->bb, next, ctx->tmp_bb);
+ rv = ap_filter_setaside_brigade(f, ctx->bb);
+ APR_BRIGADE_CONCAT(ctx->bb, ctx->tmp_bb);
+ if (rv != APR_SUCCESS) {
+ if (status == APR_SUCCESS) {
+ status = rv;
+ }
+ apr_brigade_cleanup(ctx->bb);
+ ctx->r = NULL;
+ break;
+ }
+ f->r = NULL;
+ }
}
- ap_release_brigade(f->c, tmp_bb);
+ /* Not to be leaked */
+ f->r = NULL;
return status;
}
Index: include/http_request.h
===================================================================
--- include/http_request.h (revision 1875498)
+++ include/http_request.h (working copy)
@@ -628,6 +628,51 @@ AP_DECLARE(apr_bucket *) ap_bucket_eor_create(apr_
request_rec *r);
/**
+ * Get the request of an End Of REQUEST (EOR) bucket
+ * @param e The bucket to inspect
+ * @return The request, or NULL if the bucket is not a EOR
+ */
+AP_DECLARE(request_rec *) ap_bucket_eor_get_r(apr_bucket *b);
+
+
+/** Start Of REQUEST (SOR) bucket */
+AP_DECLARE_DATA extern const apr_bucket_type_t ap_bucket_type_sor;
+
+/**
+ * Determine if a bucket is an Start Of REQUEST (SOR) bucket
+ * @param e The bucket to inspect
+ * @return true or false
+ */
+#define AP_BUCKET_IS_SOR(e) (e->type == &ap_bucket_type_sor)
+
+/**
+ * Make the bucket passed in an Start Of REQUEST (SOR) bucket
+ * @param b The bucket to make into an SOR bucket
+ * @param r The request to destroy when this bucket is destroyed
+ * @return The new bucket, or NULL if allocation failed
+ */
+AP_DECLARE(apr_bucket *) ap_bucket_sor_make(apr_bucket *b, request_rec *r);
+
+/**
+ * Create a bucket referring to an Start Of REQUEST (SOR). This bucket
+ * holds a pointer to the request_rec "owning" the following buckets,
+ * until the next SOR (e.g. suspend/resume) or End Of Request (EOR).
+ *
+ * @param list The freelist from which this bucket should be allocated
+ * @param r The request to destroy when this bucket is destroyed
+ * @return The new bucket, or NULL if allocation failed
+ */
+AP_DECLARE(apr_bucket *) ap_bucket_sor_create(apr_bucket_alloc_t *list,
+ request_rec *r);
+
+/**
+ * Get the request of a Start Of REQUEST (SOR) bucket
+ * @param e The bucket to inspect
+ * @return The request, or NULL if the bucket is not a SOR
+ */
+AP_DECLARE(request_rec *) ap_bucket_sor_get_r(apr_bucket *b);
+
+/**
* Can be used within any handler to determine if any authentication
* is required for the current request. Note that if used with an
* access_checker hook, an access_checker_ex hook or an authz provider; the
Index: server/eor_bucket.c
===================================================================
--- server/eor_bucket.c (revision 1875498)
+++ server/eor_bucket.c (working copy)
@@ -21,7 +21,7 @@
typedef struct {
apr_bucket_refcount refcount;
- request_rec *data;
+ request_rec *r;
} ap_bucket_eor;
static apr_status_t eor_bucket_cleanup(void *data)
@@ -30,11 +30,13 @@ static apr_status_t eor_bucket_cleanup(void *data)
if (*rp) {
request_rec *r = *rp;
+
/*
* If eor_bucket_destroy is called after us, this prevents
* eor_bucket_destroy from trying to destroy the pool again.
*/
*rp = NULL;
+
/* Update child status and log the transaction */
ap_update_child_status(r->connection->sbh, SERVER_BUSY_LOG, r);
ap_run_log_transaction(r);
@@ -42,14 +44,7 @@ static apr_status_t eor_bucket_cleanup(void *data)
ap_increment_counts(r->connection->sbh, r);
}
}
- return APR_SUCCESS;
-}
-static apr_status_t eor_bucket_read(apr_bucket *b, const char **str,
- apr_size_t *len, apr_read_type_e block)
-{
- *str = NULL;
- *len = 0;
return APR_SUCCESS;
}
@@ -58,7 +53,7 @@ AP_DECLARE(apr_bucket *) ap_bucket_eor_make(apr_bu
ap_bucket_eor *h;
h = apr_bucket_alloc(sizeof(*h), b->list);
- h->data = r;
+ h->r = r;
b = apr_bucket_shared_make(b, h, 0, 0);
b->type = &ap_bucket_type_eor;
@@ -85,17 +80,23 @@ AP_DECLARE(apr_bucket *) ap_bucket_eor_create(apr_
* We need to use a pre-cleanup here because a module may create a
* sub-pool which is still needed during the log_transaction hook.
*/
- apr_pool_pre_cleanup_register(r->pool, &h->data, eor_bucket_cleanup);
+ apr_pool_pre_cleanup_register(r->pool, &h->r, eor_bucket_cleanup);
}
return b;
}
+AP_DECLARE(request_rec *) ap_bucket_eor_get_r(apr_bucket *b)
+{
+ AP_DEBUG_ASSERT(AP_BUCKET_IS_EOR(b));
+ return ((ap_bucket_eor *)b->data)->r;
+}
+
static void eor_bucket_destroy(void *data)
{
ap_bucket_eor *h = data;
if (apr_bucket_shared_destroy(h)) {
- request_rec *r = h->data;
+ request_rec *r = h->r;
if (r) {
/* eor_bucket_cleanup will be called when the pool gets destroyed */
apr_pool_destroy(r->pool);
@@ -104,10 +105,55 @@ static void eor_bucket_destroy(void *data)
}
}
+
+APR_DECLARE(apr_bucket *) ap_bucket_sor_make(apr_bucket *b, request_rec *r)
+{
+ b->type = &ap_bucket_type_sor;
+ b->data = r;
+ b->length = 0;
+ b->start = 0;
+ return b;
+}
+
+APR_DECLARE(apr_bucket *) ap_bucket_sor_create(apr_bucket_alloc_t *list,
+ request_rec *r)
+{
+ apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+
+ APR_BUCKET_INIT(b);
+ b->free = apr_bucket_free;
+ b->list = list;
+ return ap_bucket_sor_make(b, r);
+}
+
+AP_DECLARE(request_rec *) ap_bucket_sor_get_r(apr_bucket *b)
+{
+ AP_DEBUG_ASSERT(AP_BUCKET_IS_SOR(b));
+ return (request_rec *)b->data;
+}
+
+
+static apr_status_t null_bucket_read(apr_bucket *b, const char **str,
+ apr_size_t *len, apr_read_type_e block)
+{
+ *str = NULL;
+ *len = 0;
+ return APR_SUCCESS;
+}
+
+APR_DECLARE_DATA const apr_bucket_type_t ap_bucket_type_sor = {
+ "SOR", 5, APR_BUCKET_METADATA,
+ apr_bucket_destroy_noop,
+ null_bucket_read,
+ apr_bucket_setaside_noop,
+ apr_bucket_split_notimpl,
+ apr_bucket_simple_copy
+};
+
AP_DECLARE_DATA const apr_bucket_type_t ap_bucket_type_eor = {
"EOR", 5, APR_BUCKET_METADATA,
eor_bucket_destroy,
- eor_bucket_read,
+ null_bucket_read,
apr_bucket_setaside_noop,
apr_bucket_split_notimpl,
apr_bucket_shared_copy