Blame SOURCES/httpd-2.4.34-CVE-2019-9511-and-9516-and-9517.patch

376e24
diff --git a/modules/http2/h2_conn.c b/modules/http2/h2_conn.c
376e24
index 2e95659..8e84666 100644
376e24
--- a/modules/http2/h2_conn.c
376e24
+++ b/modules/http2/h2_conn.c
376e24
@@ -235,6 +235,13 @@ apr_status_t h2_conn_run(struct h2_ctx *ctx, conn_rec *c)
376e24
             case H2_SESSION_ST_BUSY:
376e24
             case H2_SESSION_ST_WAIT:
376e24
                 c->cs->state = CONN_STATE_WRITE_COMPLETION;
376e24
+                if (c->cs && (session->open_streams || !session->remote.emitted_count)) {
376e24
+                    /* let the MPM know that we are not done and want
376e24
+                     * the Timeout behaviour instead of a KeepAliveTimeout
376e24
+                     * See PR 63534. 
376e24
+                     */
376e24
+                    c->cs->sense = CONN_SENSE_WANT_READ;
376e24
+                }
376e24
                 break;
376e24
             case H2_SESSION_ST_CLEANUP:
376e24
             case H2_SESSION_ST_DONE:
376e24
diff --git a/modules/http2/h2_filter.c b/modules/http2/h2_filter.c
376e24
index 8b254b1..bed6cc9 100644
376e24
--- a/modules/http2/h2_filter.c
376e24
+++ b/modules/http2/h2_filter.c
376e24
@@ -495,6 +495,52 @@ static apr_status_t status_event(void *ctx, h2_bucket_event event,
376e24
     return APR_SUCCESS;
376e24
 }
376e24
 
376e24
+static apr_status_t discard_body(request_rec *r, apr_off_t maxlen)
376e24
+{
376e24
+    apr_bucket_brigade *bb;
376e24
+    int seen_eos;
376e24
+    apr_status_t rv;
376e24
+
376e24
+    bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
376e24
+    seen_eos = 0;
376e24
+    do {
376e24
+        apr_bucket *bucket;
376e24
+
376e24
+        rv = ap_get_brigade(r->input_filters, bb, AP_MODE_READBYTES,
376e24
+                            APR_BLOCK_READ, HUGE_STRING_LEN);
376e24
+
376e24
+        if (rv != APR_SUCCESS) {
376e24
+            apr_brigade_destroy(bb);
376e24
+            return rv;
376e24
+        }
376e24
+
376e24
+        for (bucket = APR_BRIGADE_FIRST(bb);
376e24
+             bucket != APR_BRIGADE_SENTINEL(bb);
376e24
+             bucket = APR_BUCKET_NEXT(bucket))
376e24
+        {
376e24
+            const char *data;
376e24
+            apr_size_t len;
376e24
+
376e24
+            if (APR_BUCKET_IS_EOS(bucket)) {
376e24
+                seen_eos = 1;
376e24
+                break;
376e24
+            }
376e24
+            if (bucket->length == 0) {
376e24
+                continue;
376e24
+            }
376e24
+            rv = apr_bucket_read(bucket, &data, &len, APR_BLOCK_READ);
376e24
+            if (rv != APR_SUCCESS) {
376e24
+                apr_brigade_destroy(bb);
376e24
+                return rv;
376e24
+            }
376e24
+            maxlen -= bucket->length;
376e24
+        }
376e24
+        apr_brigade_cleanup(bb);
376e24
+    } while (!seen_eos && maxlen >= 0);
376e24
+
376e24
+    return APR_SUCCESS;
376e24
+}
376e24
+
376e24
 int h2_filter_h2_status_handler(request_rec *r)
376e24
 {
376e24
     h2_ctx *ctx = h2_ctx_rget(r);
376e24
@@ -513,8 +559,10 @@ int h2_filter_h2_status_handler(request_rec *r)
376e24
 
376e24
     task = ctx? h2_ctx_get_task(ctx) : NULL;
376e24
     if (task) {
376e24
-
376e24
-        if ((status = ap_discard_request_body(r)) != OK) {
376e24
+        /* In this handler, we do some special sauce to send footers back,
376e24
+         * IFF we received footers in the request. This is used in our test
376e24
+         * cases, since CGI has no way of handling those. */
376e24
+        if ((status = discard_body(r, 1024)) != OK) {
376e24
             return status;
376e24
         }
376e24
         
376e24
diff --git a/modules/http2/h2_mplx.c b/modules/http2/h2_mplx.c
376e24
index 05667ab..e67b09d 100644
376e24
--- a/modules/http2/h2_mplx.c
376e24
+++ b/modules/http2/h2_mplx.c
376e24
@@ -54,8 +54,12 @@ typedef struct {
376e24
     h2_mplx *m;
376e24
     h2_stream *stream;
376e24
     apr_time_t now;
376e24
+    apr_size_t count;
376e24
 } stream_iter_ctx;
376e24
 
376e24
+static apr_status_t mplx_be_happy(h2_mplx *m);
376e24
+static apr_status_t mplx_be_annoyed(h2_mplx *m);
376e24
+
376e24
 apr_status_t h2_mplx_child_init(apr_pool_t *pool, server_rec *s)
376e24
 {
376e24
     return APR_SUCCESS;
376e24
@@ -105,7 +109,7 @@ static void stream_input_consumed(void *ctx, h2_bucket_beam *beam, apr_off_t len
376e24
 
376e24
 static void stream_joined(h2_mplx *m, h2_stream *stream)
376e24
 {
376e24
-    ap_assert(!stream->task || stream->task->worker_done);
376e24
+    ap_assert(!h2_task_has_started(stream->task) || stream->task->worker_done);
376e24
     
376e24
     h2_ihash_remove(m->shold, stream->id);
376e24
     h2_ihash_add(m->spurge, stream);
376e24
@@ -131,7 +135,7 @@ static void stream_cleanup(h2_mplx *m, h2_stream *stream)
376e24
     h2_ififo_remove(m->readyq, stream->id);
376e24
     h2_ihash_add(m->shold, stream);
376e24
     
376e24
-    if (!stream->task || stream->task->worker_done) {
376e24
+    if (!h2_task_has_started(stream->task) || stream->task->done_done) {
376e24
         stream_joined(m, stream);
376e24
     }
376e24
     else if (stream->task) {
376e24
@@ -214,7 +218,6 @@ h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent,
376e24
         m->stream_max_mem = h2_config_geti(conf, H2_CONF_STREAM_MAX_MEM);
376e24
 
376e24
         m->streams = h2_ihash_create(m->pool, offsetof(h2_stream,id));
376e24
-        m->sredo = h2_ihash_create(m->pool, offsetof(h2_stream,id));
376e24
         m->shold = h2_ihash_create(m->pool, offsetof(h2_stream,id));
376e24
         m->spurge = h2_ihash_create(m->pool, offsetof(h2_stream,id));
376e24
         m->q = h2_iq_create(m->pool, m->max_streams);
376e24
@@ -228,8 +231,8 @@ h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent,
376e24
         m->workers = workers;
376e24
         m->max_active = workers->max_workers;
376e24
         m->limit_active = 6; /* the original h1 max parallel connections */
376e24
-        m->last_limit_change = m->last_idle_block = apr_time_now();
376e24
-        m->limit_change_interval = apr_time_from_msec(100);
376e24
+        m->last_mood_change = apr_time_now();
376e24
+        m->mood_update_interval = apr_time_from_msec(100);
376e24
         
376e24
         m->spare_slaves = apr_array_make(m->pool, 10, sizeof(conn_rec*));
376e24
         
376e24
@@ -446,6 +449,10 @@ void h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
376e24
 
376e24
     /* How to shut down a h2 connection:
376e24
      * 1. cancel all streams still active */
376e24
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c, 
376e24
+                  "h2_mplx(%ld): release, %d/%d/%d streams (total/hold/purge), %d active tasks", 
376e24
+                  m->id, (int)h2_ihash_count(m->streams),
376e24
+                  (int)h2_ihash_count(m->shold), (int)h2_ihash_count(m->spurge), m->tasks_active);
376e24
     while (!h2_ihash_iter(m->streams, stream_cancel_iter, m)) {
376e24
         /* until empty */
376e24
     }
376e24
@@ -489,8 +496,7 @@ void h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
376e24
     
376e24
     H2_MPLX_LEAVE(m);
376e24
 
376e24
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
376e24
-                  "h2_mplx(%ld): released", m->id);
376e24
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c, "h2_mplx(%ld): released", m->id);
376e24
 }
376e24
 
376e24
 apr_status_t h2_mplx_stream_cleanup(h2_mplx *m, h2_stream *stream)
376e24
@@ -728,7 +734,6 @@ static h2_task *next_stream_task(h2_mplx *m)
376e24
             }
376e24
             
376e24
             if (!stream->task) {
376e24
-
376e24
                 if (sid > m->max_stream_started) {
376e24
                     m->max_stream_started = sid;
376e24
                 }
376e24
@@ -747,9 +752,9 @@ static h2_task *next_stream_task(h2_mplx *m)
376e24
                                   "create task"));
376e24
                     return NULL;
376e24
                 }
376e24
-                
376e24
             }
376e24
             
376e24
+            stream->task->started_at = apr_time_now();
376e24
             ++m->tasks_active;
376e24
             return stream->task;
376e24
         }
376e24
@@ -788,6 +793,8 @@ static void task_done(h2_mplx *m, h2_task *task, h2_req_engine *ngn)
376e24
         /* this task was handed over to an engine for processing 
376e24
          * and the original worker has finished. That means the 
376e24
          * engine may start processing now. */
376e24
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
376e24
+                      "h2_mplx(%ld): task(%s) done (frozen)", m->id, task->id);
376e24
         h2_task_thaw(task);
376e24
         apr_thread_cond_broadcast(m->task_thawed);
376e24
         return;
376e24
@@ -827,35 +834,28 @@ static void task_done(h2_mplx *m, h2_task *task, h2_req_engine *ngn)
376e24
                   "h2_mplx(%s): request done, %f ms elapsed", task->id, 
376e24
                   (task->done_at - task->started_at) / 1000.0);
376e24
     
376e24
-    if (task->started_at > m->last_idle_block) {
376e24
-        /* this task finished without causing an 'idle block', e.g.
376e24
-         * a block by flow control.
376e24
-         */
376e24
-        if (task->done_at- m->last_limit_change >= m->limit_change_interval
376e24
-            && m->limit_active < m->max_active) {
376e24
-            /* Well behaving stream, allow it more workers */
376e24
-            m->limit_active = H2MIN(m->limit_active * 2, 
376e24
-                                     m->max_active);
376e24
-            m->last_limit_change = task->done_at;
376e24
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
376e24
-                          "h2_mplx(%ld): increase worker limit to %d",
376e24
-                          m->id, m->limit_active);
376e24
-        }
376e24
+    if (task->c && !task->c->aborted && task->started_at > m->last_mood_change) {
376e24
+        mplx_be_happy(m);
376e24
     }
376e24
     
376e24
+    ap_assert(task->done_done == 0);
376e24
+
376e24
     stream = h2_ihash_get(m->streams, task->stream_id);
376e24
     if (stream) {
376e24
         /* stream not done yet. */
376e24
-        if (!m->aborted && h2_ihash_get(m->sredo, stream->id)) {
376e24
+        if (!m->aborted && task->redo) {
376e24
             /* reset and schedule again */
376e24
+            task->worker_done = 0;
376e24
             h2_task_redo(task);
376e24
-            h2_ihash_remove(m->sredo, stream->id);
376e24
             h2_iq_add(m->q, stream->id, NULL, NULL);
376e24
+            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, m->c,
376e24
+              		  H2_STRM_MSG(stream, "redo, added to q"));
376e24
         }
376e24
         else {
376e24
             /* stream not cleaned up, stay around */
376e24
+            task->done_done = 1;
376e24
             ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
376e24
-                          H2_STRM_MSG(stream, "task_done, stream open")); 
376e24
+			  H2_STRM_MSG(stream, "task_done, stream open")); 
376e24
             if (stream->input) {
376e24
                 h2_beam_leave(stream->input);
376e24
             }
376e24
@@ -866,6 +866,7 @@ static void task_done(h2_mplx *m, h2_task *task, h2_req_engine *ngn)
376e24
     }
376e24
     else if ((stream = h2_ihash_get(m->shold, task->stream_id)) != NULL) {
376e24
         /* stream is done, was just waiting for this. */
376e24
+        task->done_done = 1;
376e24
         ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
376e24
                       H2_STRM_MSG(stream, "task_done, in hold"));
376e24
         if (stream->input) {
376e24
@@ -890,8 +891,8 @@ void h2_mplx_task_done(h2_mplx *m, h2_task *task, h2_task **ptask)
376e24
 {
376e24
     H2_MPLX_ENTER_ALWAYS(m);
376e24
 
376e24
-    task_done(m, task, NULL);
376e24
     --m->tasks_active;
376e24
+    task_done(m, task, NULL);
376e24
     
376e24
     if (m->join_wait) {
376e24
         apr_thread_cond_signal(m->join_wait);
376e24
@@ -909,91 +910,160 @@ void h2_mplx_task_done(h2_mplx *m, h2_task *task, h2_task **ptask)
376e24
  * h2_mplx DoS protection
376e24
  ******************************************************************************/
376e24
 
376e24
-static int latest_repeatable_unsubmitted_iter(void *data, void *val)
376e24
+static int timed_out_busy_iter(void *data, void *val)
376e24
 {
376e24
     stream_iter_ctx *ctx = data;
376e24
     h2_stream *stream = val;
376e24
     
376e24
-    if (stream->task && !stream->task->worker_done 
376e24
-        && h2_task_can_redo(stream->task) 
376e24
-        && !h2_ihash_get(ctx->m->sredo, stream->id)) {
376e24
-        if (!h2_stream_is_ready(stream)) {
376e24
-            /* this task occupies a worker, the response has not been submitted 
376e24
-             * yet, not been cancelled and it is a repeatable request
376e24
-             * -> it can be re-scheduled later */
376e24
-            if (!ctx->stream 
376e24
-                || (ctx->stream->task->started_at < stream->task->started_at)) {
376e24
-                /* we did not have one or this one was started later */
376e24
-                ctx->stream = stream;
376e24
-            }
376e24
-        }
376e24
+    if (h2_task_has_started(stream->task) && !stream->task->worker_done
376e24
+        && (ctx->now - stream->task->started_at) > stream->task->timeout) {
376e24
+        /* timed out stream occupying a worker, found */
376e24
+        ctx->stream = stream;
376e24
+        return 0;
376e24
     }
376e24
     return 1;
376e24
 }
376e24
 
376e24
-static h2_stream *get_latest_repeatable_unsubmitted_stream(h2_mplx *m) 
376e24
+static h2_stream *get_timed_out_busy_stream(h2_mplx *m)
376e24
 {
376e24
     stream_iter_ctx ctx;
376e24
     ctx.m = m;
376e24
     ctx.stream = NULL;
376e24
-    h2_ihash_iter(m->streams, latest_repeatable_unsubmitted_iter, &ctx;;
376e24
+    ctx.now = apr_time_now();
376e24
+    h2_ihash_iter(m->streams, timed_out_busy_iter, &ctx;;
376e24
     return ctx.stream;
376e24
 }
376e24
 
376e24
-static int timed_out_busy_iter(void *data, void *val)
376e24
+static int latest_repeatable_unsubmitted_iter(void *data, void *val)
376e24
 {
376e24
     stream_iter_ctx *ctx = data;
376e24
     h2_stream *stream = val;
376e24
-    if (stream->task && !stream->task->worker_done
376e24
-        && (ctx->now - stream->task->started_at) > stream->task->timeout) {
376e24
-        /* timed out stream occupying a worker, found */
376e24
-        ctx->stream = stream;
376e24
-        return 0;
376e24
+    
376e24
+    if (!stream->task) goto leave;
376e24
+    if (!h2_task_has_started(stream->task) || stream->task->worker_done) goto leave;
376e24
+    if (h2_stream_is_ready(stream)) goto leave;
376e24
+    if (stream->task->redo) {
376e24
+        ++ctx->count;
376e24
+        goto leave;
376e24
+    }
376e24
+    if (h2_task_can_redo(stream->task)) {
376e24
+        /* this task occupies a worker, the response has not been submitted
376e24
+         * yet, not been cancelled and it is a repeatable request
376e24
+         * -> we could redo it later */
376e24
+        if (!ctx->stream
376e24
+            || (ctx->stream->task->started_at < stream->task->started_at)) {
376e24
+            /* we did not have one or this one was started later */
376e24
+            ctx->stream = stream;
376e24
+        }
376e24
     }
376e24
+leave:
376e24
     return 1;
376e24
 }
376e24
 
376e24
-static h2_stream *get_timed_out_busy_stream(h2_mplx *m) 
376e24
+static apr_status_t assess_task_to_throttle(h2_task **ptask, h2_mplx *m)
376e24
 {
376e24
     stream_iter_ctx ctx;
376e24
+    
376e24
+    /* count the running tasks already marked for redo and get one that could
376e24
+     * be throttled */
376e24
+    *ptask = NULL;
376e24
     ctx.m = m;
376e24
     ctx.stream = NULL;
376e24
-    ctx.now = apr_time_now();
376e24
-    h2_ihash_iter(m->streams, timed_out_busy_iter, &ctx;;
376e24
-    return ctx.stream;
376e24
+    ctx.count = 0;
376e24
+    h2_ihash_iter(m->streams, latest_repeatable_unsubmitted_iter, &ctx;;
376e24
+    if (m->tasks_active - ctx.count > m->limit_active) {
376e24
+        /* we are above the limit of running tasks, accounting for the ones
376e24
+         * already throttled. */
376e24
+        if (ctx.stream && ctx.stream->task) {
376e24
+            *ptask = ctx.stream->task;
376e24
+            return APR_EAGAIN;
376e24
+        }
376e24
+        /* above limit, be seeing no candidate for easy throttling */
376e24
+        if (get_timed_out_busy_stream(m)) {
376e24
+            /* Too many busy workers, unable to cancel enough streams
376e24
+             * and with a busy, timed out stream, we tell the client
376e24
+             * to go away... */
376e24
+            return APR_TIMEUP;
376e24
+        }
376e24
+    }
376e24
+    return APR_SUCCESS;
376e24
 }
376e24
 
376e24
 static apr_status_t unschedule_slow_tasks(h2_mplx *m) 
376e24
 {
376e24
-    h2_stream *stream;
376e24
-    int n;
376e24
+    h2_task *task;
376e24
+    apr_status_t rv;    
376e24
     
376e24
     /* Try to get rid of streams that occupy workers. Look for safe requests
376e24
      * that are repeatable. If none found, fail the connection.
376e24
      */
376e24
-    n = (m->tasks_active - m->limit_active - (int)h2_ihash_count(m->sredo));
376e24
-    while (n > 0 && (stream = get_latest_repeatable_unsubmitted_stream(m))) {
376e24
-        h2_task_rst(stream->task, H2_ERR_CANCEL);
376e24
-        h2_ihash_add(m->sredo, stream);
376e24
-        --n;
376e24
+    while (APR_EAGAIN == (rv = assess_task_to_throttle(&task, m))) {
376e24
+        task->redo = 1;
376e24
+        h2_task_rst(task, H2_ERR_CANCEL);
376e24
     }
376e24
     
376e24
-    if ((m->tasks_active - h2_ihash_count(m->sredo)) > m->limit_active) {
376e24
-        h2_stream *stream = get_timed_out_busy_stream(m);
376e24
-        if (stream) {
376e24
-            /* Too many busy workers, unable to cancel enough streams
376e24
-             * and with a busy, timed out stream, we tell the client
376e24
-             * to go away... */
376e24
-            return APR_TIMEUP;
376e24
-        }
376e24
+    return rv;
376e24
+}
376e24
+
376e24
+static apr_status_t mplx_be_happy(h2_mplx *m)
376e24
+{
376e24
+    apr_time_t now;
376e24
+    
376e24
+    --m->irritations_since;
376e24
+    now = apr_time_now();
376e24
+    if (m->limit_active < m->max_active
376e24
+        && (now - m->last_mood_change >= m->mood_update_interval
376e24
+            || m->irritations_since < -m->limit_active)) {
376e24
+        m->limit_active = H2MIN(m->limit_active * 2, m->max_active);
376e24
+        m->last_mood_change = now;
376e24
+        m->irritations_since = 0;
376e24
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
376e24
+                      "h2_mplx(%ld): mood update, increasing worker limit to %d",
376e24
+                      m->id, m->limit_active);
376e24
     }
376e24
     return APR_SUCCESS;
376e24
+}    
376e24
+
376e24
+static apr_status_t mplx_be_annoyed(h2_mplx *m)
376e24
+{
376e24
+    apr_status_t status = APR_SUCCESS;
376e24
+    apr_time_t now;
376e24
+
376e24
+    ++m->irritations_since;
376e24
+    now = apr_time_now();
376e24
+    if (m->limit_active > 2 &&
376e24
+        ((now - m->last_mood_change >= m->mood_update_interval)
376e24
+         || (m->irritations_since >= m->limit_active))) {
376e24
+         
376e24
+         if (m->limit_active > 16) {
376e24
+             m->limit_active = 16;
376e24
+         }
376e24
+         else if (m->limit_active > 8) {
376e24
+             m->limit_active = 8;
376e24
+         }
376e24
+         else if (m->limit_active > 4) {
376e24
+             m->limit_active = 4;
376e24
+         }
376e24
+         else if (m->limit_active > 2) {
376e24
+             m->limit_active = 2;
376e24
+         }
376e24
+         m->last_mood_change = now;
376e24
+         m->irritations_since = 0;
376e24
+         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
376e24
+                       "h2_mplx(%ld): mood update, decreasing worker limit to %d",
376e24
+                       m->id, m->limit_active);
376e24
+    }
376e24
+    
376e24
+    if (m->tasks_active > m->limit_active) {
376e24
+        status = unschedule_slow_tasks(m);
376e24
+    }
376e24
+    
376e24
+    return status;
376e24
 }
376e24
 
376e24
 apr_status_t h2_mplx_idle(h2_mplx *m)
376e24
 {
376e24
     apr_status_t status = APR_SUCCESS;
376e24
-    apr_time_t now;            
376e24
     apr_size_t scount;
376e24
     
376e24
     H2_MPLX_ENTER(m);
376e24
@@ -1013,31 +1083,7 @@ apr_status_t h2_mplx_idle(h2_mplx *m)
376e24
              * of busy workers we allow for this connection until it
376e24
              * well behaves.
376e24
              */
376e24
-            now = apr_time_now();
376e24
-            m->last_idle_block = now;
376e24
-            if (m->limit_active > 2 
376e24
-                && now - m->last_limit_change >= m->limit_change_interval) {
376e24
-                if (m->limit_active > 16) {
376e24
-                    m->limit_active = 16;
376e24
-                }
376e24
-                else if (m->limit_active > 8) {
376e24
-                    m->limit_active = 8;
376e24
-                }
376e24
-                else if (m->limit_active > 4) {
376e24
-                    m->limit_active = 4;
376e24
-                }
376e24
-                else if (m->limit_active > 2) {
376e24
-                    m->limit_active = 2;
376e24
-                }
376e24
-                m->last_limit_change = now;
376e24
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
376e24
-                              "h2_mplx(%ld): decrease worker limit to %d",
376e24
-                              m->id, m->limit_active);
376e24
-            }
376e24
-            
376e24
-            if (m->tasks_active > m->limit_active) {
376e24
-                status = unschedule_slow_tasks(m);
376e24
-            }
376e24
+            status = mplx_be_annoyed(m);
376e24
         }
376e24
         else if (!h2_iq_empty(m->q)) {
376e24
             ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
376e24
@@ -1201,8 +1247,8 @@ void h2_mplx_req_engine_done(h2_req_engine *ngn, conn_rec *r_conn,
376e24
         
376e24
         if (status != APR_SUCCESS && stream 
376e24
             && h2_task_can_redo(task) 
376e24
-            && !h2_ihash_get(m->sredo, stream->id)) {
376e24
-            h2_ihash_add(m->sredo, stream);
376e24
+            && !task->redo) {
376e24
+            task->redo = 1;
376e24
         }
376e24
 
376e24
         if (task->engine) { 
376e24
@@ -1268,11 +1314,24 @@ int h2_mplx_awaits_data(h2_mplx *m)
376e24
     if (h2_ihash_empty(m->streams)) {
376e24
         waiting = 0;
376e24
     }
376e24
-    else if (!m->tasks_active && !h2_ififo_count(m->readyq)
376e24
-             && h2_iq_empty(m->q)) {
376e24
+    else if (!m->tasks_active && !h2_ififo_count(m->readyq) && h2_iq_empty(m->q)) {
376e24
         waiting = 0;
376e24
     }
376e24
 
376e24
     H2_MPLX_LEAVE(m);
376e24
     return waiting;
376e24
 }
376e24
+
376e24
+apr_status_t h2_mplx_client_rst(h2_mplx *m, int stream_id)
376e24
+{
376e24
+    h2_stream *stream;
376e24
+    apr_status_t status = APR_SUCCESS;
376e24
+    
376e24
+    H2_MPLX_ENTER_ALWAYS(m);
376e24
+    stream = h2_ihash_get(m->streams, stream_id);
376e24
+    if (stream && stream->task) {
376e24
+        status = mplx_be_annoyed(m);
376e24
+    }
376e24
+    H2_MPLX_LEAVE(m);
376e24
+    return status;
376e24
+}
376e24
diff --git a/modules/http2/h2_mplx.h b/modules/http2/h2_mplx.h
376e24
index 2890b98..24a725d 100644
376e24
--- a/modules/http2/h2_mplx.h
376e24
+++ b/modules/http2/h2_mplx.h
376e24
@@ -65,7 +65,6 @@ struct h2_mplx {
376e24
     unsigned int is_registered;     /* is registered at h2_workers */
376e24
 
376e24
     struct h2_ihash_t *streams;     /* all streams currently processing */
376e24
-    struct h2_ihash_t *sredo;       /* all streams that need to be re-started */
376e24
     struct h2_ihash_t *shold;       /* all streams done with task ongoing */
376e24
     struct h2_ihash_t *spurge;      /* all streams done, ready for destroy */
376e24
     
376e24
@@ -79,10 +78,10 @@ struct h2_mplx {
376e24
     int tasks_active;       /* # of tasks being processed from this mplx */
376e24
     int limit_active;       /* current limit on active tasks, dynamic */
376e24
     int max_active;         /* max, hard limit # of active tasks in a process */
376e24
-    apr_time_t last_idle_block;      /* last time, this mplx entered IDLE while
376e24
-                                      * streams were ready */
376e24
-    apr_time_t last_limit_change;    /* last time, worker limit changed */
376e24
-    apr_interval_time_t limit_change_interval;
376e24
+    
376e24
+    apr_time_t last_mood_change; /* last time, we worker limit changed */
376e24
+    apr_interval_time_t mood_update_interval; /* how frequent we update at most */
376e24
+    int irritations_since; /* irritations (>0) or happy events (<0) since last mood change */
376e24
 
376e24
     apr_thread_mutex_t *lock;
376e24
     struct apr_thread_cond_t *added_output;
376e24
@@ -211,6 +210,8 @@ typedef int h2_mplx_stream_cb(struct h2_stream *s, void *ctx);
376e24
 
376e24
 apr_status_t h2_mplx_stream_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx);
376e24
 
376e24
+apr_status_t h2_mplx_client_rst(h2_mplx *m, int stream_id);
376e24
+
376e24
 /*******************************************************************************
376e24
  * Output handling of streams.
376e24
  ******************************************************************************/
376e24
diff --git a/modules/http2/h2_session.c b/modules/http2/h2_session.c
376e24
index a1b31d2..ad5303c 100644
376e24
--- a/modules/http2/h2_session.c
376e24
+++ b/modules/http2/h2_session.c
376e24
@@ -390,9 +390,14 @@ static int on_frame_recv_cb(nghttp2_session *ng2s,
376e24
                           (int)frame->rst_stream.error_code);
376e24
             stream = h2_session_stream_get(session, frame->hd.stream_id);
376e24
             if (stream && stream->initiated_on) {
376e24
+                /* A stream reset on a request we sent it. Normal, when the
376e24
+                 * client does not want it. */
376e24
                 ++session->pushes_reset;
376e24
             }
376e24
             else {
376e24
+                /* A stream reset on a request it sent us. Could happen in a browser
376e24
+                 * when the user navigates away or cancels loading - maybe. */
376e24
+                h2_mplx_client_rst(session->mplx, frame->hd.stream_id);
376e24
                 ++session->streams_reset;
376e24
             }
376e24
             break;
376e24
@@ -1676,7 +1681,7 @@ static void transit(h2_session *session, const char *action, h2_session_state ns
376e24
                      * that already served requests - not fair. */
376e24
                     session->idle_sync_until = apr_time_now() + apr_time_from_sec(1);
376e24
                     s = "timeout";
376e24
-                    timeout = H2MAX(session->s->timeout, session->s->keep_alive_timeout);
376e24
+                    timeout = session->s->timeout;
376e24
                     update_child_status(session, SERVER_BUSY_READ, "idle");
376e24
                     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, 
376e24
                                   H2_SSSN_LOG("", session, "enter idle, timeout = %d sec"), 
376e24
@@ -1684,8 +1689,8 @@ static void transit(h2_session *session, const char *action, h2_session_state ns
376e24
                 }
376e24
                 else if (session->open_streams) {
376e24
                     s = "timeout";
376e24
-                    timeout = session->s->keep_alive_timeout;
376e24
-                    update_child_status(session, SERVER_BUSY_KEEPALIVE, "idle");
376e24
+                    timeout = session->s->timeout;
376e24
+                    update_child_status(session, SERVER_BUSY_READ, "idle");
376e24
                 }
376e24
                 else {
376e24
                     /* normal keepalive setup */
376e24
@@ -2142,6 +2147,14 @@ apr_status_t h2_session_process(h2_session *session, int async)
376e24
                         session->have_read = 1;
376e24
                     }
376e24
                     else if (APR_STATUS_IS_EAGAIN(status) || APR_STATUS_IS_TIMEUP(status)) {
376e24
+                        status = h2_mplx_idle(session->mplx);
376e24
+                        if (status == APR_EAGAIN) {
376e24
+                            break;
376e24
+                        }
376e24
+                        else if (status != APR_SUCCESS) {
376e24
+                            dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 
376e24
+                                           H2_ERR_ENHANCE_YOUR_CALM, "less is more");
376e24
+                        }
376e24
                         status = APR_EAGAIN;
376e24
                         goto out;
376e24
                     }
376e24
diff --git a/modules/http2/h2_stream.c b/modules/http2/h2_stream.c
376e24
index 22c5902..8689dc8 100644
376e24
--- a/modules/http2/h2_stream.c
376e24
+++ b/modules/http2/h2_stream.c
376e24
@@ -398,13 +398,8 @@ apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags, size_
376e24
                 /* start pushed stream */
376e24
                 ap_assert(stream->request == NULL);
376e24
                 ap_assert(stream->rtmp != NULL);
376e24
-                status = h2_request_end_headers(stream->rtmp, stream->pool, 1, 0);
376e24
-                if (status != APR_SUCCESS) {
376e24
-                    return status;
376e24
-                }
376e24
-                set_policy_for(stream, stream->rtmp);
376e24
-                stream->request = stream->rtmp;
376e24
-                stream->rtmp = NULL;
376e24
+                status = h2_stream_end_headers(stream, 1, 0);
376e24
+                if (status != APR_SUCCESS) goto leave;
376e24
             break;
376e24
             
376e24
         default:
376e24
@@ -416,6 +411,7 @@ apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags, size_
376e24
     if (status == APR_SUCCESS && eos) {
376e24
         status = transit(stream, on_event(stream, H2_SEV_CLOSED_L));
376e24
     }
376e24
+leave:
376e24
     return status;
376e24
 }
376e24
 
376e24
@@ -456,13 +452,8 @@ apr_status_t h2_stream_recv_frame(h2_stream *stream, int ftype, int flags, size_
376e24
                      * to abort the connection here, since this is clearly a protocol error */
376e24
                     return APR_EINVAL;
376e24
                 }
376e24
-                status = h2_request_end_headers(stream->rtmp, stream->pool, eos, frame_len);
376e24
-                if (status != APR_SUCCESS) {
376e24
-                    return status;
376e24
-                }
376e24
-                set_policy_for(stream, stream->rtmp);
376e24
-                stream->request = stream->rtmp;
376e24
-                stream->rtmp = NULL;
376e24
+                status = h2_stream_end_headers(stream, eos, frame_len);
376e24
+                if (status != APR_SUCCESS) goto leave;
376e24
             }
376e24
             break;
376e24
             
376e24
@@ -473,6 +464,7 @@ apr_status_t h2_stream_recv_frame(h2_stream *stream, int ftype, int flags, size_
376e24
     if (status == APR_SUCCESS && eos) {
376e24
         status = transit(stream, on_event(stream, H2_SEV_CLOSED_R));
376e24
     }
376e24
+leave:
376e24
     return status;
376e24
 }
376e24
 
376e24
@@ -684,6 +676,8 @@ static apr_status_t add_trailer(h2_stream *stream,
376e24
     hvalue = apr_pstrndup(stream->pool, value, vlen);
376e24
     h2_util_camel_case_header(hname, nlen);
376e24
     apr_table_mergen(stream->trailers, hname, hvalue);
376e24
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, 
376e24
+                  H2_STRM_MSG(stream, "added trailer '%s: %s'"), hname, hvalue);
376e24
     
376e24
     return APR_SUCCESS;
376e24
 }
376e24
@@ -703,15 +697,19 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
376e24
     if (name[0] == ':') {
376e24
         if ((vlen) > session->s->limit_req_line) {
376e24
             /* pseudo header: approximation of request line size check */
376e24
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
376e24
-                          H2_STRM_MSG(stream, "pseudo %s too long"), name);
376e24
+            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,  
376e24
+                          H2_STRM_LOG(APLOGNO(10178), stream, 
376e24
+                                      "Request pseudo header exceeds "
376e24
+                                      "LimitRequestFieldSize: %s"), name);
376e24
             error = HTTP_REQUEST_URI_TOO_LARGE;
376e24
         }
376e24
     }
376e24
     else if ((nlen + 2 + vlen) > session->s->limit_req_fieldsize) {
376e24
         /* header too long */
376e24
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
376e24
-                      H2_STRM_MSG(stream, "header %s too long"), name);
376e24
+        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c,  
376e24
+                      H2_STRM_LOG(APLOGNO(10180), stream,"Request header exceeds "
376e24
+                                  "LimitRequestFieldSize: %.*s"),
376e24
+                      (int)H2MIN(nlen, 80), name);
376e24
         error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
376e24
     }
376e24
     
376e24
@@ -723,8 +721,9 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
376e24
             h2_stream_rst(stream, H2_ERR_ENHANCE_YOUR_CALM);
376e24
             return APR_ECONNRESET;
376e24
         }
376e24
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
376e24
-                      H2_STRM_MSG(stream, "too many header lines")); 
376e24
+        ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, session->c, 
376e24
+                      H2_STRM_LOG(APLOGNO(10181), stream, "Number of request headers "
376e24
+                                  "exceeds LimitRequestFields"));
376e24
         error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
376e24
     }
376e24
     
376e24
@@ -755,6 +754,47 @@ apr_status_t h2_stream_add_header(h2_stream *stream,
376e24
     return status;
376e24
 }
376e24
 
376e24
+typedef struct {
376e24
+    apr_size_t maxlen;
376e24
+    const char *failed_key;
376e24
+} val_len_check_ctx;
376e24
+
376e24
+static int table_check_val_len(void *baton, const char *key, const char *value)
376e24
+{
376e24
+    val_len_check_ctx *ctx = baton;
376e24
+
376e24
+    if (strlen(value) <= ctx->maxlen) return 1;
376e24
+    ctx->failed_key = key;
376e24
+    return 0;
376e24
+}
376e24
+
376e24
+apr_status_t h2_stream_end_headers(h2_stream *stream, int eos, size_t raw_bytes)
376e24
+{
376e24
+    apr_status_t status;
376e24
+    val_len_check_ctx ctx;
376e24
+    
376e24
+    status = h2_request_end_headers(stream->rtmp, stream->pool, eos, raw_bytes);
376e24
+    if (APR_SUCCESS == status) {
376e24
+        set_policy_for(stream, stream->rtmp);
376e24
+        stream->request = stream->rtmp;
376e24
+        stream->rtmp = NULL;
376e24
+        
376e24
+        ctx.maxlen = stream->session->s->limit_req_fieldsize;
376e24
+        ctx.failed_key = NULL;
376e24
+        apr_table_do(table_check_val_len, &ctx, stream->request->headers, NULL);
376e24
+        if (ctx.failed_key) {
376e24
+            ap_log_cerror(APLOG_MARK, APLOG_INFO, 0, stream->session->c,  
376e24
+                          H2_STRM_LOG(APLOGNO(), stream,"Request header exceeds "
376e24
+                                      "LimitRequestFieldSize: %.*s"),
376e24
+                          (int)H2MIN(strlen(ctx.failed_key), 80), ctx.failed_key);
376e24
+            set_error_response(stream, HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE);
376e24
+            /* keep on returning APR_SUCCESS, so that we send a HTTP response and
376e24
+             * do not RST the stream. */
376e24
+        }
376e24
+    }
376e24
+    return status;
376e24
+}
376e24
+
376e24
 static apr_bucket *get_first_headers_bucket(apr_bucket_brigade *bb)
376e24
 {
376e24
     if (bb) {
376e24
diff --git a/modules/http2/h2_stream.h b/modules/http2/h2_stream.h
376e24
index 7ecc0ad..79cb39d 100644
376e24
--- a/modules/http2/h2_stream.h
376e24
+++ b/modules/http2/h2_stream.h
376e24
@@ -198,6 +198,10 @@ apr_status_t h2_stream_set_request_rec(h2_stream *stream,
376e24
 apr_status_t h2_stream_add_header(h2_stream *stream,
376e24
                                   const char *name, size_t nlen,
376e24
                                   const char *value, size_t vlen);
376e24
+                                  
376e24
+/* End the contruction of request headers */
376e24
+apr_status_t h2_stream_end_headers(h2_stream *stream, int eos, size_t raw_bytes);
376e24
+
376e24
 
376e24
 apr_status_t h2_stream_send_frame(h2_stream *stream, int frame_type, int flags, size_t frame_len);
376e24
 apr_status_t h2_stream_recv_frame(h2_stream *stream, int frame_type, int flags, size_t frame_len);
376e24
diff --git a/modules/http2/h2_task.c b/modules/http2/h2_task.c
376e24
index 86fb026..1c01830 100644
376e24
--- a/modules/http2/h2_task.c
376e24
+++ b/modules/http2/h2_task.c
376e24
@@ -425,8 +425,15 @@ int h2_task_can_redo(h2_task *task) {
376e24
             || !strcmp("OPTIONS", task->request->method));
376e24
 }
376e24
 
376e24
+int h2_task_has_started(h2_task *task)
376e24
+{
376e24
+    return task && task->started_at != 0;
376e24
+}
376e24
+
376e24
 void h2_task_redo(h2_task *task)
376e24
 {
376e24
+    task->started_at = 0;
376e24
+    task->worker_done = 0;
376e24
     task->rst_error = 0;
376e24
 }
376e24
 
376e24
@@ -565,7 +572,6 @@ apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread, int worker_id)
376e24
     ap_assert(task);
376e24
     c = task->c;
376e24
     task->worker_started = 1;
376e24
-    task->started_at = apr_time_now();
376e24
     
376e24
     if (c->master) {
376e24
         /* Each conn_rec->id is supposed to be unique at a point in time. Since
376e24
diff --git a/modules/http2/h2_task.h b/modules/http2/h2_task.h
376e24
index ab6a746..56f6e0f 100644
376e24
--- a/modules/http2/h2_task.h
376e24
+++ b/modules/http2/h2_task.h
376e24
@@ -83,7 +83,10 @@ struct h2_task {
376e24
     unsigned int frozen         : 1;
376e24
     unsigned int thawed         : 1;
376e24
     unsigned int worker_started : 1; /* h2_worker started processing */
376e24
-    unsigned int worker_done    : 1; /* h2_worker finished */
376e24
+    unsigned int redo : 1;           /* was throttled, should be restarted later */    
376e24
+    
376e24
+    int worker_done;                 /* h2_worker finished */
376e24
+    int done_done;                   /* task_done has been handled */
376e24
     
376e24
     apr_time_t started_at;           /* when processing started */
376e24
     apr_time_t done_at;              /* when processing was done */
376e24
@@ -105,6 +108,7 @@ apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread, int worker_id);
376e24
 
376e24
 void h2_task_redo(h2_task *task);
376e24
 int h2_task_can_redo(h2_task *task);
376e24
+int h2_task_has_started(h2_task *task);
376e24
 
376e24
 /**
376e24
  * Reset the task with the given error code, resets all input/output.
376e24
diff --git a/server/mpm/event/event.c b/server/mpm/event/event.c
376e24
index 16e39be..2543693 100644
376e24
--- a/server/mpm/event/event.c
376e24
+++ b/server/mpm/event/event.c
376e24
@@ -1111,10 +1111,11 @@ read_request:
376e24
                           "network write failure in core output filter");
376e24
             cs->pub.state = CONN_STATE_LINGER;
376e24
         }
376e24
-        else if (c->data_in_output_filters) {
376e24
+        else if (c->data_in_output_filters ||
376e24
+                 cs->pub.sense == CONN_SENSE_WANT_READ) {
376e24
             /* Still in WRITE_COMPLETION_STATE:
376e24
-             * Set a write timeout for this connection, and let the
376e24
-             * event thread poll for writeability.
376e24
+             * Set a read/write timeout for this connection, and let the
376e24
+             * event thread poll for read/writeability.
376e24
              */
376e24
             cs->queue_timestamp = apr_time_now();
376e24
             notify_suspend(cs);