]> granicus.if.org Git - apache/commitdiff
mod_http2: some DoS protection, fix for read after free
authorStefan Eissing <icing@apache.org>
Tue, 1 Mar 2016 17:19:25 +0000 (17:19 +0000)
committerStefan Eissing <icing@apache.org>
Tue, 1 Mar 2016 17:19:25 +0000 (17:19 +0000)
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1733113 13f79535-47bb-0310-9956-ffa450edef68

12 files changed:
CHANGES
modules/http2/h2_io.c
modules/http2/h2_io.h
modules/http2/h2_mplx.c
modules/http2/h2_mplx.h
modules/http2/h2_push.c
modules/http2/h2_request.c
modules/http2/h2_request.h
modules/http2/h2_session.c
modules/http2/h2_stream.c
modules/http2/h2_version.h
modules/http2/h2_workers.c

diff --git a/CHANGES b/CHANGES
index 496dc73b87bc5d7cf5f683add0e3969e41f50b32..8abcea4ed19a25c98c767bfdaa72108ae3f89789 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,18 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache 2.5.0
 
+  *) mod_http2: Fixed possible read after free when streams were cancelled early
+     by the client. 
+     Fixed apr_uint64_t formatting in a log statement to user proper APR def.
+     Number of worker threads allowed to a connection is adjusting dynamically.
+     Starting with 4, the number is doubled when streams can be served without
+     the server ever having to wait on the client. The number is halfed, when
+     the server has to wait on flow control grants. This can happen with a 
+     maximum frequency of 5 times per second. When a connection occupies too
+     many workers, repeatable requests (GET/HEAD/OPTIONS) are cancelled and
+     placed back in the queue. Should that not suffice and a stream is busy
+     longer than the server timeout, the connection will be aborted.
+  
   *) mod_ssl: Fix a possible memory leak on restart for custom [EC]DH params.
      [Jan Kaluza, Yann Ylavic]
 
index f88ac4d88042def409081aeb00bfe6d7b1cea21f..a54e8763b76b30620364460c2878c27036dcb65f 100644 (file)
 #include "h2_task.h"
 #include "h2_util.h"
 
-h2_io *h2_io_create(int id, apr_pool_t *pool)
+h2_io *h2_io_create(int id, apr_pool_t *pool, const h2_request *request)
 {
     h2_io *io = apr_pcalloc(pool, sizeof(*io));
     if (io) {
         io->id = id;
         io->pool = pool;
         io->bucket_alloc = apr_bucket_alloc_create(pool);
+        io->request = h2_request_clone(pool, request);
     }
     return io;
 }
 
+void h2_io_redo(h2_io *io)
+{
+    io->worker_started = 0;
+    io->response = NULL;
+    io->rst_error = 0;
+    if (io->bbin) {
+        apr_brigade_cleanup(io->bbin);
+    }
+    if (io->bbout) {
+        apr_brigade_cleanup(io->bbout);
+    }
+    if (io->tmp) {
+        apr_brigade_cleanup(io->tmp);
+    }
+    io->started_at = io->done_at = 0;
+}
+
+int h2_io_is_repeatable(h2_io *io) {
+    if (io->submitted
+        || io->input_consumed > 0 
+        || !io->request) {
+        /* cannot repeat that. */
+        return 0;
+    }
+    return (!strcmp("GET", io->request->method)
+            || !strcmp("HEAD", io->request->method)
+            || !strcmp("OPTIONS", io->request->method));
+}
+
 void h2_io_set_response(h2_io *io, h2_response *response) 
 {
     AP_DEBUG_ASSERT(io->pool);
index 7c704a4d8eba76992c88aaba6d36eea07a9b22e4..bfe42a96b479caf574e78f94859c277e159d9cf3 100644 (file)
@@ -49,8 +49,9 @@ struct h2_io {
     apr_bucket_brigade *tmp;         /* temporary data for chunking */
 
     unsigned int orphaned       : 1; /* h2_stream is gone for this io */    
-    unsigned int processing_started : 1; /* h2_worker started processing for this io */
-    unsigned int processing_done: 1; /* h2_worker finished for this io */
+    unsigned int worker_started : 1; /* h2_worker started processing for this io */
+    unsigned int worker_done    : 1; /* h2_worker finished for this io */
+    unsigned int submitted      : 1; /* response has been submitted to client */
     unsigned int request_body   : 1; /* iff request has body */
     unsigned int eos_in         : 1; /* input eos has been seen */
     unsigned int eos_in_written : 1; /* input eos has been forwarded */
@@ -61,6 +62,8 @@ struct h2_io {
     struct apr_thread_cond_t *timed_cond; /* condition to wait on, maybe NULL */
     apr_time_t timeout_at;           /* when IO wait will time out */
     
+    apr_time_t started_at;           /* when processing started */
+    apr_time_t done_at;              /* when processing was done */
     apr_size_t input_consumed;       /* how many bytes have been read */
         
     int files_handles_owned;
@@ -73,7 +76,7 @@ struct h2_io {
 /**
  * Creates a new h2_io for the given stream id. 
  */
-h2_io *h2_io_create(int id, apr_pool_t *pool);
+h2_io *h2_io_create(int id, apr_pool_t *pool, const struct h2_request *request);
 
 /**
  * Set the response of this stream.
@@ -85,6 +88,9 @@ void h2_io_set_response(h2_io *io, struct h2_response *response);
  */
 void h2_io_rst(h2_io *io, int error);
 
+int h2_io_is_repeatable(h2_io *io);
+void h2_io_redo(h2_io *io);
+
 /**
  * The input data is completely queued. Blocked reads will return immediately
  * and give either data or EOF.
index 6311d67d4e717c37d24bcb8ae3e5ebe6e5f8ef61..b40a137ebddf66f55be8e4b02227061528295bee 100644 (file)
@@ -209,7 +209,11 @@ h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent,
         m->stream_max_mem = h2_config_geti(conf, H2_CONF_STREAM_MAX_MEM);
         m->stream_timeout = stream_timeout;
         m->workers = workers;
-        m->workers_max = 6;
+        m->workers_max = h2_config_geti(conf, H2_CONF_MAX_WORKERS);
+        m->workers_def_limit = 4;
+        m->workers_limit = m->workers_def_limit;
+        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;
@@ -276,6 +280,9 @@ static void io_destroy(h2_mplx *m, h2_io *io, int events)
 
     h2_io_set_remove(m->stream_ios, io);
     h2_io_set_remove(m->ready_ios, io);
+    if (m->redo_ios) {
+        h2_io_set_remove(m->redo_ios, io);
+    }
     
     if (pool) {
         apr_pool_clear(pool);
@@ -292,7 +299,7 @@ static int io_stream_done(h2_mplx *m, h2_io *io, int rst_error)
 {
     /* Remove io from ready set, we will never submit it */
     h2_io_set_remove(m->ready_ios, io);
-    if (!io->processing_started || io->processing_done) {
+    if (!io->worker_started || io->worker_done) {
         /* already finished or not even started yet */
         h2_iq_remove(m->q, io->id);
         io_destroy(m, io, 1);
@@ -321,7 +328,7 @@ static int stream_print(void *ctx, h2_io *io)
                       io->request->method, io->request->authority, io->request->path,
                       io->response? "http" : (io->rst_error? "reset" : "?"),
                       io->response? io->response->http_status : io->rst_error,
-                      io->orphaned, io->processing_started, io->processing_done,
+                      io->orphaned, io->worker_started, io->worker_done,
                       io->eos_in, io->eos_out);
     }
     else if (io) {
@@ -331,7 +338,7 @@ static int stream_print(void *ctx, h2_io *io)
                       m->id, io->id, 
                       io->response? "http" : (io->rst_error? "reset" : "?"),
                       io->response? io->response->http_status : io->rst_error,
-                      io->orphaned, io->processing_started, io->processing_done,
+                      io->orphaned, io->worker_started, io->worker_done,
                       io->eos_in, io->eos_out);
     }
     else {
@@ -647,6 +654,7 @@ h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_ihash_t *streams)
         if (io && !m->aborted) {
             stream = h2_ihash_get(streams, io->id);
             if (stream) {
+                io->submitted = 1;
                 if (io->rst_error) {
                     h2_stream_rst(stream, io->rst_error);
                 }
@@ -667,7 +675,7 @@ h2_stream *h2_mplx_next_submit(h2_mplx *m, h2_ihash_t *streams)
                               "resetting io to close request processing",
                               m->id, io->id);
                 h2_io_make_orphaned(io, H2_ERR_STREAM_CLOSED);
-                if (!io->processing_started || io->processing_done) {
+                if (!io->worker_started || io->worker_done) {
                     io_destroy(m, io, 1);
                 }
                 else {
@@ -989,7 +997,7 @@ apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx)
     return status;
 }
 
-static h2_io *open_io(h2_mplx *m, int stream_id)
+static h2_io *open_io(h2_mplx *m, int stream_id, const h2_request *request)
 {
     apr_pool_t *io_pool = m->spare_pool;
     h2_io *io;
@@ -1002,7 +1010,7 @@ static h2_io *open_io(h2_mplx *m, int stream_id)
         m->spare_pool = NULL;
     }
     
-    io = h2_io_create(stream_id, io_pool);
+    io = h2_io_create(stream_id, io_pool, request);
     h2_io_set_add(m->stream_ios, io);
     
     return io;
@@ -1022,8 +1030,7 @@ apr_status_t h2_mplx_process(h2_mplx *m, int stream_id, const h2_request *req,
             status = APR_ECONNABORTED;
         }
         else {
-            h2_io *io = open_io(m, stream_id);
-            io->request = req;
+            h2_io *io = open_io(m, stream_id, req);
             
             if (!io->request->body) {
                 status = h2_io_in_close(io);
@@ -1050,15 +1057,21 @@ static h2_task *pop_task(h2_mplx *m)
     h2_task *task = NULL;
     int sid;
     while (!m->aborted && !task 
-        && (m->workers_busy < m->workers_max)
+        && (m->workers_busy < m->workers_limit)
         && (sid = h2_iq_shift(m->q)) > 0) {
         h2_io *io = h2_io_set_get(m->stream_ios, sid);
-        if (io) {
+        if (io && io->orphaned) {
+            io_destroy(m, io, 0);
+            if (m->join_wait) {
+                apr_thread_cond_signal(m->join_wait);
+            }
+        }
+        else if (io) {
             conn_rec *slave = h2_slave_create(m->c, m->pool, m->spare_allocator);
             m->spare_allocator = NULL;
             task = h2_task_create(m->id, io->request, slave, m);
-            
-            io->processing_started = 1;
+            io->worker_started = 1;
+            io->started_at = apr_time_now();
             if (sid > m->max_stream_started) {
                 m->max_stream_started = sid;
             }
@@ -1102,6 +1115,7 @@ static void task_done(h2_mplx *m, h2_task *task)
         }
         else {
             h2_io *io = h2_io_set_get(m->stream_ios, task->stream_id);
+            
             ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
                           "h2_mplx(%ld): task(%s) done", m->id, task->id);
             /* clean our references and report request as done. Signal
@@ -1117,7 +1131,39 @@ static void task_done(h2_mplx *m, h2_task *task)
             h2_slave_destroy(task->c, &m->spare_allocator);
             task = NULL;
             if (io) {
-                io->processing_done = 1;
+                apr_time_t now = apr_time_now();
+                if (!io->orphaned && m->redo_ios
+                    && h2_io_set_get(m->redo_ios, io->id)) {
+                    /* reset and schedule again */
+                    h2_io_redo(io);
+                    h2_io_set_remove(m->redo_ios, io);
+                    h2_iq_add(m->q, io->id, NULL, NULL);
+                }
+                else {
+                    io->worker_done = 1;
+                    io->done_at = now;
+                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                                  "h2_mplx(%ld): request(%d) done, %f ms"
+                                  " elapsed", m->id, io->id, 
+                                  (io->done_at - io->started_at) / 1000.0);
+                    if (io->started_at > m->last_idle_block) {
+                        /* this task finished without causing an 'idle block', e.g.
+                         * a block by flow control.
+                         */
+                        if (now - m->last_limit_change >= m->limit_change_interval
+                            && m->workers_limit < m->workers_max) {
+                            /* Well behaving stream, allow it more workers */
+                            m->workers_limit = H2MIN(m->workers_limit * 2, 
+                                                     m->workers_max);
+                            m->last_limit_change = now;
+                            m->need_registration = 1;
+                            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                                          "h2_mplx(%ld): increase worker limit to %d",
+                                          m->id, m->workers_limit);
+                        }
+                    }
+                }
+                
                 if (io->orphaned) {
                     io_destroy(m, io, 0);
                     if (m->join_wait) {
@@ -1131,12 +1177,11 @@ static void task_done(h2_mplx *m, h2_task *task)
             apr_thread_cond_broadcast(m->task_done);
         }
     }
-    
 }
 
 void h2_mplx_task_done(h2_mplx *m, h2_task *task, h2_task **ptask)
 {
-    int acquired, do_registration = 0;
+    int acquired;
     
     if (enter_mutex(m, &acquired) == APR_SUCCESS) {
         task_done(m, task);
@@ -1145,12 +1190,147 @@ void h2_mplx_task_done(h2_mplx *m, h2_task *task, h2_task **ptask)
             /* caller wants another task */
             *ptask = pop_task(m);
         }
-        do_registration = (m->workers_busy+1 == m->workers_max);
         leave_mutex(m, acquired);
     }
-    if (do_registration) {
-        workers_register(m);
+}
+
+/*******************************************************************************
+ * h2_mplx DoS protection
+ ******************************************************************************/
+
+typedef struct {
+    h2_mplx *m;
+    h2_io *io;
+    apr_time_t now;
+} io_iter_ctx;
+
+static int latest_repeatable_busy_unsubmitted_iter(void *data, h2_io *io)
+{
+    io_iter_ctx *ctx = data;
+    if (io->worker_started && !io->worker_done
+        && h2_io_is_repeatable(io)
+        && !h2_io_set_get(ctx->m->redo_ios, io->id)) {
+        /* this io occupies a worker, the response has not been submitted yet,
+         * not been cancelled and it is a repeatable request
+         * -> it can be re-scheduled later */
+        if (!ctx->io || ctx->io->started_at < io->started_at) {
+            /* we did not have one or this one was started later */
+            ctx->io = io;
+        }
+    }
+    return 1;
+}
+
+static h2_io *get_latest_repeatable_busy_unsubmitted_io(h2_mplx *m) 
+{
+    io_iter_ctx ctx;
+    ctx.m = m;
+    ctx.io = NULL;
+    h2_io_set_iter(m->stream_ios, latest_repeatable_busy_unsubmitted_iter, &ctx);
+    return ctx.io;
+}
+
+static int timed_out_busy_iter(void *data, h2_io *io)
+{
+    io_iter_ctx *ctx = data;
+    if (io->worker_started && !io->worker_done
+        && (ctx->now - io->started_at) > ctx->m->stream_timeout) {
+        /* timed out stream occupying a worker, found */
+        ctx->io = io;
+        return 0;
     }
+    return 1;
+}
+static h2_io *get_timed_out_busy_stream(h2_mplx *m) 
+{
+    io_iter_ctx ctx;
+    ctx.m = m;
+    ctx.io = NULL;
+    ctx.now = apr_time_now();
+    h2_io_set_iter(m->stream_ios, timed_out_busy_iter, &ctx);
+    return ctx.io;
+}
+
+static apr_status_t unschedule_slow_ios(h2_mplx *m) 
+{
+    h2_io *io;
+    int n;
+    
+    if (!m->redo_ios) {
+        m->redo_ios = h2_io_set_create(m->pool);
+    }
+    /* Try to get rid of streams that occupy workers. Look for safe requests
+     * that are repeatable. If none found, fail the connection.
+     */
+    n = (m->workers_busy - m->workers_limit - h2_io_set_size(m->redo_ios));
+    while (n > 0 && (io = get_latest_repeatable_busy_unsubmitted_io(m))) {
+        h2_io_set_add(m->redo_ios, io);
+        h2_io_rst(io, H2_ERR_CANCEL);
+        --n;
+    }
+    
+    if ((m->workers_busy - h2_io_set_size(m->redo_ios)) > m->workers_limit) {
+        io = get_timed_out_busy_stream(m);
+        if (io) {
+            /* Too many busy workers, unable to cancel enough streams
+             * and with a busy, timed out stream, we tell the client
+             * to go away... */
+            return APR_TIMEUP;
+        }
+    }
+    return APR_SUCCESS;
+}
+
+apr_status_t h2_mplx_idle(h2_mplx *m)
+{
+    apr_status_t status = APR_SUCCESS;
+    apr_time_t now;            
+    int acquired;
+    
+    if (enter_mutex(m, &acquired) == APR_SUCCESS) {
+        apr_size_t scount = h2_io_set_size(m->stream_ios);
+        if (scount > 0 && m->workers_busy) {
+            /* If we have streams in connection state 'IDLE', meaning
+             * all streams are ready to sent data out, but lack
+             * WINDOW_UPDATEs. 
+             * 
+             * This is ok, unless we have streams that still occupy
+             * h2 workers. As worker threads are a scarce resource, 
+             * we need to take measures that we do not get DoSed.
+             * 
+             * This is what we call an 'idle block'. Limit the amount 
+             * of busy workers we allow for this connection until it
+             * well behaves.
+             */
+            now = apr_time_now();
+            m->last_idle_block = now;
+            if (m->workers_limit > 2 
+                && now - m->last_limit_change >= m->limit_change_interval) {
+                if (m->workers_limit > 16) {
+                    m->workers_limit = 16;
+                }
+                else if (m->workers_limit > 8) {
+                    m->workers_limit = 8;
+                }
+                else if (m->workers_limit > 4) {
+                    m->workers_limit = 4;
+                }
+                else if (m->workers_limit > 2) {
+                    m->workers_limit = 2;
+                }
+                m->last_limit_change = now;
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                              "h2_mplx(%ld): decrease worker limit to %d",
+                              m->id, m->workers_limit);
+            }
+            
+            if (m->workers_busy > m->workers_limit) {
+                status = unschedule_slow_ios(m);
+            }
+        }
+        leave_mutex(m, acquired);
+    }
+    return status;
 }
 
 /*******************************************************************************
index 8dff6e0853770ebadb87f1e1dce85270cdf390ae..4d6ce7c0d5a5d0d4181cd567367803ba525fab13 100644 (file)
@@ -68,15 +68,22 @@ struct h2_mplx {
     apr_pool_t *pool;
 
     unsigned int aborted : 1;
+    unsigned int need_registration : 1;
 
     struct h2_int_queue *q;
     struct h2_io_set *stream_ios;
     struct h2_io_set *ready_ios;
+    struct h2_io_set *redo_ios;
     
     int max_stream_started;      /* highest stream id that started processing */
     int workers_busy;            /* # of workers processing on this mplx */
-    int workers_max;             /* max # of workers occupied by this mplx */
-    int need_registration;
+    int workers_limit;           /* current # of workers limit, dynamic */
+    int workers_def_limit;       /* default # of workers limit */
+    int workers_max;             /* max, hard limit # of workers in a process */
+    apr_time_t last_idle_block;  /* last time, this mplx entered IDLE while
+                                  * streams were ready */
+    apr_time_t last_limit_change;/* last time, worker limit changed */
+    apr_interval_time_t limit_change_interval;
 
     apr_thread_mutex_t *lock;
     struct apr_thread_cond_t *added_output;
@@ -389,6 +396,16 @@ APR_RING_INSERT_TAIL((b), ap__b, h2_mplx, link);   \
  */
 #define H2_MPLX_REMOVE(e)      APR_RING_REMOVE((e), link)
 
+/*******************************************************************************
+ * h2_mplx DoS protection
+ ******************************************************************************/
+
+/**
+ * Master connection has entered idle mode.
+ * @param m the mplx instance of the master connection
+ * @return != SUCCESS iff connection should be terminated
+ */
+apr_status_t h2_mplx_idle(h2_mplx *m);
 
 /*******************************************************************************
  * h2_mplx h2_req_engine handling.
index c0325ff4191e167b1bb2e4995bbab366c107c6f7..748e32abbfd74803508942aa7459cf04a781d89e 100644 (file)
@@ -778,7 +778,7 @@ static apr_status_t gset_encode_next(gset_encoder *encoder, apr_uint64_t pval)
     /* Intentional no APLOGNO */
     ap_log_perror(APLOG_MARK, GCSLOG_LEVEL, 0, encoder->pool,
                   "h2_push_diary_enc: val=%"APR_UINT64_T_HEX_FMT", delta=%"
-                  APR_UINT64_T_HEX_FMT" flex_bits=%" APR_UINT64_T_FMT
+                  APR_UINT64_T_HEX_FMT" flex_bits=%"APR_UINT64_T_FMT", "
                   ", fixed_bits=%d, fixed_val=%"APR_UINT64_T_HEX_FMT, 
                   pval, delta, flex_bits, encoder->fixed_bits, delta&encoder->fixed_mask);
     for (; flex_bits != 0; --flex_bits) {
index 9aa8d49e5eaa6b7d12f7b12ec5bf8c9195633701..2767ef538a465c6ce4896749192394714578bece 100644 (file)
@@ -60,10 +60,6 @@ h2_request *h2_request_createn(int id, apr_pool_t *pool,
     return req;
 }
 
-void h2_request_destroy(h2_request *req)
-{
-}
-
 static apr_status_t inspect_clen(h2_request *req, const char *s)
 {
     char *end;
@@ -342,11 +338,22 @@ void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src)
     dst->authority      = OPT_COPY(p, src->authority);
     dst->path           = OPT_COPY(p, src->path);
     dst->headers        = apr_table_clone(p, src->headers);
+    if (src->trailers) {
+        dst->trailers   = apr_table_clone(p, src->trailers);
+    }
     dst->content_length = src->content_length;
     dst->chunked        = src->chunked;
     dst->eoh            = src->eoh;
 }
 
+h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src)
+{
+    h2_request *nreq = apr_pcalloc(p, sizeof(*nreq));
+    memcpy(nreq, src, sizeof(*nreq));
+    h2_request_copy(p, nreq, src);
+    return nreq;
+}
+
 request_rec *h2_request_create_rec(const h2_request *req, conn_rec *conn)
 {
     request_rec *r;
index 946bd34852493cedca35efaf50e43896754a1086..da87d70a50228cbd053d2322ceb0655489240853 100644 (file)
@@ -30,8 +30,6 @@ apr_status_t h2_request_make(h2_request *req, apr_pool_t *pool,
                              const char *authority, const char *path, 
                              apr_table_t *headers);
 
-void h2_request_destroy(h2_request *req);
-
 apr_status_t h2_request_rwrite(h2_request *req, request_rec *r);
 
 apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
@@ -47,6 +45,8 @@ apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool,
 
 void h2_request_copy(apr_pool_t *p, h2_request *dst, const h2_request *src);
 
+h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src);
+
 /**
  * Create a request_rec representing the h2_request to be
  * processed on the given connection.
index 4019bd4d356fe70367117b7151dae37750a3215f..33f82fad9ce068b4e973df0f210ef8d5968cba58 100644 (file)
@@ -2015,7 +2015,7 @@ apr_status_t h2_session_process(h2_session *session, int async)
                 no_streams = h2_ihash_is_empty(session->streams);
                 update_child_status(session, (no_streams? SERVER_BUSY_KEEPALIVE
                                               : SERVER_BUSY_READ), "idle");
-                if (async && !session->r && session->requests_received && no_streams) {
+                if (async && no_streams && !session->r && session->requests_received) {
                     ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, c,
                                   "h2_session(%ld): async idle, nonblock read", session->id);
                     /* We do not return to the async mpm immediately, since under
@@ -2051,7 +2051,13 @@ apr_status_t h2_session_process(h2_session *session, int async)
                 }
                 else {
                     /* We wait in smaller increments, using a 1 second timeout.
-                     * That gives us the chance to check for MPMQ_STOPPING often. */
+                     * That gives us the chance to check for MPMQ_STOPPING often. 
+                     */
+                    status = h2_mplx_idle(session->mplx);
+                    if (status != APR_SUCCESS) {
+                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 
+                                       H2_ERR_ENHANCE_YOUR_CALM, "less is more");
+                    }
                     h2_filter_cin_timeout_set(session->cin, apr_time_from_sec(1));
                     status = h2_session_read(session, 1);
                     if (status == APR_SUCCESS) {
index 8af65673a4c4ef1a33ee58979782917a367ee6c0..29df7afd82352d84990bbdaf89dda5d97cd12847 100644 (file)
@@ -169,11 +169,6 @@ h2_stream *h2_stream_open(int id, apr_pool_t *pool, h2_session *session)
 apr_status_t h2_stream_destroy(h2_stream *stream)
 {
     AP_DEBUG_ASSERT(stream);
-    if (stream->request) {
-        h2_request_destroy(stream->request);
-        stream->request = NULL;
-    }
-    
     if (stream->pool) {
         apr_pool_destroy(stream->pool);
     }
index f02560cedb90f54d45d23b66b894c1fc178e9aa0..b828cf784dc53efa95dd589ed190cef3ad56a37c 100644 (file)
@@ -26,7 +26,7 @@
  * @macro
  * Version number of the http2 module as c string
  */
-#define MOD_HTTP2_VERSION "1.3.1-DEV"
+#define MOD_HTTP2_VERSION "1.3.2-DEV"
 
 /**
  * @macro
@@ -34,7 +34,7 @@
  * 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 0x010301
+#define MOD_HTTP2_VERSION_NUM 0x010302
 
 
 #endif /* mod_h2_h2_version_h */
index 6b6897cbe59ecec55719666f84f794a329e59f98..2c1dc8dab42d5add5aa4fcb918f92b39a4fd46d4 100644 (file)
@@ -86,7 +86,6 @@ static h2_task *next_task(h2_workers *workers)
         --workers->mplx_count;
         
         task = h2_mplx_pop_task(m, &has_more);
-        
         if (has_more) {
             H2_MPLX_LIST_INSERT_TAIL(&workers->mplxs, m);
             ++workers->mplx_count;