diff -up --new-file httpd-2.4.23/modules/http2/config2.m4 /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/config2.m4
--- httpd-2.4.23/modules/http2/config2.m4	2016-06-28 21:57:30.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/config2.m4	2017-10-17 13:47:14.000000000 +0200
@@ -21,7 +21,6 @@ http2_objs="dnl
 mod_http2.lo dnl
 h2_alt_svc.lo dnl
 h2_bucket_beam.lo dnl
-h2_bucket_eoc.lo dnl
 h2_bucket_eos.lo dnl
 h2_config.lo dnl
 h2_conn.lo dnl
@@ -30,17 +29,16 @@ h2_ctx.lo dnl
 h2_filter.lo dnl
 h2_from_h1.lo dnl
 h2_h2.lo dnl
+h2_headers.lo dnl
 h2_mplx.lo dnl
 h2_ngn_shed.lo dnl
 h2_push.lo dnl
 h2_request.lo dnl
-h2_response.lo dnl
 h2_session.lo dnl
 h2_stream.lo dnl
 h2_switch.lo dnl
 h2_task.lo dnl
 h2_util.lo dnl
-h2_worker.lo dnl
 h2_workers.lo dnl
 "
 
@@ -82,12 +80,18 @@ AC_DEFUN([APACHE_CHECK_NGHTTP2],[
     if test -n "$PKGCONFIG"; then
       saved_PKG_CONFIG_PATH="$PKG_CONFIG_PATH"
       AC_MSG_CHECKING([for pkg-config along $PKG_CONFIG_PATH])
-      if test "x$ap_nghttp2_base" != "x" -a \
-              -f "${ap_nghttp2_base}/lib/pkgconfig/libnghttp2.pc"; then
-        dnl Ensure that the given path is used by pkg-config too, otherwise
-        dnl the system libnghttp2.pc might be picked up instead.
-        PKG_CONFIG_PATH="${ap_nghttp2_base}/lib/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
-        export PKG_CONFIG_PATH
+      if test "x$ap_nghttp2_base" != "x" ; then
+        if test -f "${ap_nghttp2_base}/lib/pkgconfig/libnghttp2.pc"; then
+          dnl Ensure that the given path is used by pkg-config too, otherwise
+          dnl the system libnghttp2.pc might be picked up instead.
+          PKG_CONFIG_PATH="${ap_nghttp2_base}/lib/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
+          export PKG_CONFIG_PATH
+        elif test -f "${ap_nghttp2_base}/lib64/pkgconfig/libnghttp2.pc"; then
+          dnl Ensure that the given path is used by pkg-config too, otherwise
+          dnl the system libnghttp2.pc might be picked up instead.
+          PKG_CONFIG_PATH="${ap_nghttp2_base}/lib64/pkgconfig${PKG_CONFIG_PATH+:}${PKG_CONFIG_PATH}"
+          export PKG_CONFIG_PATH
+        fi
       fi
       AC_ARG_ENABLE(nghttp2-staticlib-deps,APACHE_HELP_STRING(--enable-nghttp2-staticlib-deps,[link mod_http2 with dependencies of libnghttp2's static libraries (as indicated by "pkg-config --static"). Must be specified in addition to --enable-http2.]), [
         if test "$enableval" = "yes"; then
@@ -154,6 +158,12 @@ dnl # nghttp2 >= 1.3.0: access to stream
 dnl # nghttp2 >= 1.5.0: changing stream priorities
       AC_CHECK_FUNCS([nghttp2_session_change_stream_priority], 
         [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_CHANGE_PRIO"])], [])
+dnl # nghttp2 >= 1.14.0: invalid header callback
+      AC_CHECK_FUNCS([nghttp2_session_callbacks_set_on_invalid_header_callback], 
+        [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_INVALID_HEADER_CB"])], [])
+dnl # nghttp2 >= 1.15.0: get/set stream window sizes
+      AC_CHECK_FUNCS([nghttp2_session_get_stream_local_window_size], 
+        [APR_ADDTO(MOD_CPPFLAGS, ["-DH2_NG2_LOCAL_WIN_SIZE"])], [])
     else
       AC_MSG_WARN([nghttp2 version is too old])
     fi
diff -up --new-file httpd-2.4.23/modules/http2/h2_alt_svc.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_alt_svc.c
--- httpd-2.4.23/modules/http2/h2_alt_svc.c	2016-02-10 00:09:24.000000000 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_alt_svc.c	2016-07-20 18:09:06.000000000 +0200
@@ -86,7 +86,7 @@ static int h2_alt_svc_handler(request_re
         return DECLINED;
     }
     
-    cfg = h2_config_rget(r);
+    cfg = h2_config_sget(r->server);
     if (r->hostname && cfg && cfg->alt_svcs && cfg->alt_svcs->nelts > 0) {
         const char *alt_svc_used = apr_table_get(r->headers_in, "Alt-Svc-Used");
         if (!alt_svc_used) {
diff -up --new-file httpd-2.4.23/modules/http2/h2_bucket_beam.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_bucket_beam.c
--- httpd-2.4.23/modules/http2/h2_bucket_beam.c	2016-06-09 12:38:10.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_bucket_beam.c	2017-10-13 10:37:45.000000000 +0200
@@ -14,6 +14,7 @@
  */
 
 #include <apr_lib.h>
+#include <apr_atomic.h>
 #include <apr_strings.h>
 #include <apr_time.h>
 #include <apr_buckets.h>
@@ -21,6 +22,7 @@
 #include <apr_thread_cond.h>
 
 #include <httpd.h>
+#include <http_protocol.h>
 #include <http_log.h>
 
 #include "h2_private.h"
@@ -66,7 +68,7 @@ struct h2_beam_proxy {
     apr_bucket_refcount refcount;
     APR_RING_ENTRY(h2_beam_proxy) link;
     h2_bucket_beam *beam;
-    apr_bucket *bred;
+    apr_bucket *bsender;
     apr_size_t n;
 };
 
@@ -76,9 +78,9 @@ static apr_status_t beam_bucket_read(apr
                                      apr_size_t *len, apr_read_type_e block)
 {
     h2_beam_proxy *d = b->data;
-    if (d->bred) {
+    if (d->bsender) {
         const char *data;
-        apr_status_t status = apr_bucket_read(d->bred, &data, len, block);
+        apr_status_t status = apr_bucket_read(d->bsender, &data, len, block);
         if (status == APR_SUCCESS) {
             *str = data + b->start;
             *len = b->length;
@@ -109,24 +111,24 @@ static void beam_bucket_destroy(void *da
 
 static apr_bucket * h2_beam_bucket_make(apr_bucket *b, 
                                         h2_bucket_beam *beam,
-                                        apr_bucket *bred, apr_size_t n)
+                                        apr_bucket *bsender, apr_size_t n)
 {
     h2_beam_proxy *d;
 
     d = apr_bucket_alloc(sizeof(*d), b->list);
     H2_BPROXY_LIST_INSERT_TAIL(&beam->proxies, d);
     d->beam = beam;
-    d->bred = bred;
+    d->bsender = bsender;
     d->n = n;
     
-    b = apr_bucket_shared_make(b, d, 0, bred? bred->length : 0);
+    b = apr_bucket_shared_make(b, d, 0, bsender? bsender->length : 0);
     b->type = &h2_bucket_type_beam;
 
     return b;
 }
 
 static apr_bucket *h2_beam_bucket_create(h2_bucket_beam *beam,
-                                         apr_bucket *bred,
+                                         apr_bucket *bsender,
                                          apr_bucket_alloc_t *list,
                                          apr_size_t n)
 {
@@ -135,28 +137,9 @@ static apr_bucket *h2_beam_bucket_create
     APR_BUCKET_INIT(b);
     b->free = apr_bucket_free;
     b->list = list;
-    return h2_beam_bucket_make(b, beam, bred, n);
+    return h2_beam_bucket_make(b, beam, bsender, n);
 }
 
-/*static apr_status_t beam_bucket_setaside(apr_bucket *b, apr_pool_t *pool)
-{
-    apr_status_t status = APR_SUCCESS;
-    h2_beam_proxy *d = b->data;
-    if (d->bred) {
-        const char *data;
-        apr_size_t len;
-        
-        status = apr_bucket_read(d->bred, &data, &len, APR_BLOCK_READ);
-        if (status == APR_SUCCESS) {
-            b = apr_bucket_heap_make(b, (char *)data + b->start, b->length, NULL);
-            if (b == NULL) {
-                return APR_ENOMEM;
-            }
-        }
-    }
-    return status;
-}*/
-
 const apr_bucket_type_t h2_bucket_type_beam = {
     "BEAM", 5, APR_BUCKET_DATA,
     beam_bucket_destroy,
@@ -169,51 +152,65 @@ const apr_bucket_type_t h2_bucket_type_b
 /*******************************************************************************
  * h2_blist, a brigade without allocations
  ******************************************************************************/
- 
-apr_size_t h2_util_bl_print(char *buffer, apr_size_t bmax, 
-                            const char *tag, const char *sep, 
-                            h2_blist *bl)
+
+static apr_array_header_t *beamers;
+
+static apr_status_t cleanup_beamers(void *dummy)
 {
-    apr_size_t off = 0;
-    const char *sp = "";
-    apr_bucket *b;
-    
-    if (bl) {
-        memset(buffer, 0, bmax--);
-        off += apr_snprintf(buffer+off, bmax-off, "%s(", tag);
-        for (b = H2_BLIST_FIRST(bl); 
-             bmax && (b != H2_BLIST_SENTINEL(bl));
-             b = APR_BUCKET_NEXT(b)) {
+    (void)dummy;
+    beamers = NULL;
+    return APR_SUCCESS;
+}
+
+void h2_register_bucket_beamer(h2_bucket_beamer *beamer)
+{
+    if (!beamers) {
+        apr_pool_cleanup_register(apr_hook_global_pool, NULL,
+                                  cleanup_beamers, apr_pool_cleanup_null);
+        beamers = apr_array_make(apr_hook_global_pool, 10, 
+                                 sizeof(h2_bucket_beamer*));
+    }
+    APR_ARRAY_PUSH(beamers, h2_bucket_beamer*) = beamer;
+}
+
+static apr_bucket *h2_beam_bucket(h2_bucket_beam *beam, 
+                                  apr_bucket_brigade *dest,
+                                  const apr_bucket *src)
+{
+    apr_bucket *b = NULL;
+    int i;
+    if (beamers) {
+        for (i = 0; i < beamers->nelts && b == NULL; ++i) {
+            h2_bucket_beamer *beamer;
             
-            off += h2_util_bucket_print(buffer+off, bmax-off, b, sp);
-            sp = " ";
+            beamer = APR_ARRAY_IDX(beamers, i, h2_bucket_beamer*);
+            b = beamer(beam, dest, src);
         }
-        off += apr_snprintf(buffer+off, bmax-off, ")%s", sep);
-    }
-    else {
-        off += apr_snprintf(buffer+off, bmax-off, "%s(null)%s", tag, sep);
     }
-    return off;
+    return b;
 }
 
 
-
 /*******************************************************************************
  * bucket beam that can transport buckets across threads
  ******************************************************************************/
 
+static void mutex_leave(void *ctx, apr_thread_mutex_t *lock)
+{
+    apr_thread_mutex_unlock(lock);
+}
+
+static apr_status_t mutex_enter(void *ctx, h2_beam_lock *pbl)
+{
+    h2_bucket_beam *beam = ctx;
+    pbl->mutex = beam->lock;
+    pbl->leave = mutex_leave;
+    return apr_thread_mutex_lock(pbl->mutex);
+}
+
 static apr_status_t enter_yellow(h2_bucket_beam *beam, h2_beam_lock *pbl)
 {
-    h2_beam_mutex_enter *enter = beam->m_enter;
-    if (enter) {
-        void *ctx = beam->m_ctx;
-        if (ctx) {
-            return enter(ctx, pbl);
-        }
-    }
-    pbl->mutex = NULL;
-    pbl->leave = NULL;
-    return APR_SUCCESS;
+    return mutex_enter(beam, pbl);
 }
 
 static void leave_yellow(h2_bucket_beam *beam, h2_beam_lock *pbl)
@@ -223,18 +220,65 @@ static void leave_yellow(h2_bucket_beam
     }
 }
 
-static apr_off_t calc_buffered(h2_bucket_beam *beam)
+static apr_off_t bucket_mem_used(apr_bucket *b)
 {
-    apr_off_t len = 0;
+    if (APR_BUCKET_IS_FILE(b)) {
+        return 0;
+    }
+    else {
+        /* should all have determinate length */
+        return b->length;
+    }
+}
+
+static int report_consumption(h2_bucket_beam *beam, h2_beam_lock *pbl)
+{
+    int rv = 0;
+    apr_off_t len = beam->received_bytes - beam->cons_bytes_reported;
+    h2_beam_io_callback *cb = beam->cons_io_cb;
+     
+    if (len > 0) {
+        if (cb) {
+            void *ctx = beam->cons_ctx;
+            
+            if (pbl) leave_yellow(beam, pbl);
+            cb(ctx, beam, len);
+            if (pbl) enter_yellow(beam, pbl);
+            rv = 1;
+        }
+        beam->cons_bytes_reported += len;
+    }
+    return rv;
+}
+
+static void report_prod_io(h2_bucket_beam *beam, int force, h2_beam_lock *pbl)
+{
+    apr_off_t len = beam->sent_bytes - beam->prod_bytes_reported;
+    if (force || len > 0) {
+        h2_beam_io_callback *cb = beam->prod_io_cb; 
+        if (cb) {
+            void *ctx = beam->prod_ctx;
+            
+            leave_yellow(beam, pbl);
+            cb(ctx, beam, len);
+            enter_yellow(beam, pbl);
+        }
+        beam->prod_bytes_reported += len;
+    }
+}
+
+static apr_size_t calc_buffered(h2_bucket_beam *beam)
+{
+    apr_size_t len = 0;
     apr_bucket *b;
-    for (b = H2_BLIST_FIRST(&beam->red); 
-         b != H2_BLIST_SENTINEL(&beam->red);
+    for (b = H2_BLIST_FIRST(&beam->send_list); 
+         b != H2_BLIST_SENTINEL(&beam->send_list);
          b = APR_BUCKET_NEXT(b)) {
         if (b->length == ((apr_size_t)-1)) {
             /* do not count */
         }
         else if (APR_BUCKET_IS_FILE(b)) {
-            /* if unread, has no real mem footprint. how to test? */
+            /* if unread, has no real mem footprint. */
         }
         else {
             len += b->length;
@@ -243,14 +287,14 @@ static apr_off_t calc_buffered(h2_bucket
     return len;
 }
 
-static void r_purge_reds(h2_bucket_beam *beam)
+static void r_purge_sent(h2_bucket_beam *beam)
 {
-    apr_bucket *bred;
-    /* delete all red buckets in purge brigade, needs to be called
-     * from red thread only */
-    while (!H2_BLIST_EMPTY(&beam->purge)) {
-        bred = H2_BLIST_FIRST(&beam->purge);
-        apr_bucket_delete(bred);
+    apr_bucket *b;
+    /* delete all sender buckets in purge brigade, needs to be called
+     * from sender thread only */
+    while (!H2_BLIST_EMPTY(&beam->purge_list)) {
+        b = H2_BLIST_FIRST(&beam->purge_list);
+        apr_bucket_delete(b);
     }
 }
 
@@ -263,30 +307,80 @@ static apr_size_t calc_space_left(h2_buc
     return APR_SIZE_MAX;
 }
 
-static apr_status_t wait_cond(h2_bucket_beam *beam, apr_thread_mutex_t *lock)
+static int buffer_is_empty(h2_bucket_beam *beam)
+{
+    return ((!beam->recv_buffer || APR_BRIGADE_EMPTY(beam->recv_buffer))
+            && H2_BLIST_EMPTY(&beam->send_list));
+}
+
+static apr_status_t wait_empty(h2_bucket_beam *beam, apr_read_type_e block,  
+                               apr_thread_mutex_t *lock)
 {
-    if (beam->timeout > 0) {
-        return apr_thread_cond_timedwait(beam->m_cond, lock, beam->timeout);
+    apr_status_t rv = APR_SUCCESS;
+    
+    while (!buffer_is_empty(beam) && APR_SUCCESS == rv) {
+        if (APR_BLOCK_READ != block || !lock) {
+            rv = APR_EAGAIN;
+        }
+        else if (beam->timeout > 0) {
+            rv = apr_thread_cond_timedwait(beam->change, lock, beam->timeout);
+        }
+        else {
+            rv = apr_thread_cond_wait(beam->change, lock);
+        }
     }
-    else {
-        return apr_thread_cond_wait(beam->m_cond, lock);
+    return rv;
+}
+
+static apr_status_t wait_not_empty(h2_bucket_beam *beam, apr_read_type_e block,  
+                                   apr_thread_mutex_t *lock)
+{
+    apr_status_t rv = APR_SUCCESS;
+    
+    while (buffer_is_empty(beam) && APR_SUCCESS == rv) {
+        if (beam->aborted) {
+            rv = APR_ECONNABORTED;
+        }
+        else if (beam->closed) {
+            rv = APR_EOF;
+        }
+        else if (APR_BLOCK_READ != block || !lock) {
+            rv = APR_EAGAIN;
+        }
+        else if (beam->timeout > 0) {
+            rv = apr_thread_cond_timedwait(beam->change, lock, beam->timeout);
+        }
+        else {
+            rv = apr_thread_cond_wait(beam->change, lock);
+        }
     }
+    return rv;
 }
 
-static apr_status_t r_wait_space(h2_bucket_beam *beam, apr_read_type_e block,
-                                 h2_beam_lock *pbl, apr_off_t *premain) 
+static apr_status_t wait_not_full(h2_bucket_beam *beam, apr_read_type_e block, 
+                                  apr_size_t *pspace_left, h2_beam_lock *bl)
 {
-    *premain = calc_space_left(beam);
-    while (!beam->aborted && *premain <= 0 
-           && (block == APR_BLOCK_READ) && pbl->mutex) {
-        apr_status_t status = wait_cond(beam, pbl->mutex);
-        if (APR_STATUS_IS_TIMEUP(status)) {
-            return status;
+    apr_status_t rv = APR_SUCCESS;
+    apr_size_t left;
+    
+    while (0 == (left = calc_space_left(beam)) && APR_SUCCESS == rv) {
+        if (beam->aborted) {
+            rv = APR_ECONNABORTED;
+        }
+        else if (block != APR_BLOCK_READ || !bl->mutex) {
+            rv = APR_EAGAIN;
+        }
+        else {
+            if (beam->timeout > 0) {
+                rv = apr_thread_cond_timedwait(beam->change, bl->mutex, beam->timeout);
+            }
+            else {
+                rv = apr_thread_cond_wait(beam->change, bl->mutex);
+            }
         }
-        r_purge_reds(beam);
-        *premain = calc_space_left(beam);
     }
-    return beam->aborted? APR_ECONNABORTED : APR_SUCCESS;
+    *pspace_left = left;
+    return rv;
 }
 
 static void h2_beam_emitted(h2_bucket_beam *beam, h2_beam_proxy *proxy)
@@ -298,34 +392,34 @@ static void h2_beam_emitted(h2_bucket_be
         /* even when beam buckets are split, only the one where
          * refcount drops to 0 will call us */
         H2_BPROXY_REMOVE(proxy);
-        /* invoked from green thread, the last beam bucket for the red
-         * bucket bred is about to be destroyed.
+        /* invoked from receiver thread, the last beam bucket for the send
+         * bucket is about to be destroyed.
          * remove it from the hold, where it should be now */
-        if (proxy->bred) {
-            for (b = H2_BLIST_FIRST(&beam->hold); 
-                 b != H2_BLIST_SENTINEL(&beam->hold);
+        if (proxy->bsender) {
+            for (b = H2_BLIST_FIRST(&beam->hold_list); 
+                 b != H2_BLIST_SENTINEL(&beam->hold_list);
                  b = APR_BUCKET_NEXT(b)) {
-                 if (b == proxy->bred) {
+                 if (b == proxy->bsender) {
                     break;
                  }
             }
-            if (b != H2_BLIST_SENTINEL(&beam->hold)) {
+            if (b != H2_BLIST_SENTINEL(&beam->hold_list)) {
                 /* bucket is in hold as it should be, mark this one
                  * and all before it for purging. We might have placed meta
-                 * buckets without a green proxy into the hold before it 
+                 * buckets without a receiver proxy into the hold before it 
                  * and schedule them for purging now */
-                for (b = H2_BLIST_FIRST(&beam->hold); 
-                     b != H2_BLIST_SENTINEL(&beam->hold);
+                for (b = H2_BLIST_FIRST(&beam->hold_list); 
+                     b != H2_BLIST_SENTINEL(&beam->hold_list);
                      b = next) {
                     next = APR_BUCKET_NEXT(b);
-                    if (b == proxy->bred) {
+                    if (b == proxy->bsender) {
                         APR_BUCKET_REMOVE(b);
-                        H2_BLIST_INSERT_TAIL(&beam->purge, b);
+                        H2_BLIST_INSERT_TAIL(&beam->purge_list, b);
                         break;
                     }
                     else if (APR_BUCKET_IS_METADATA(b)) {
                         APR_BUCKET_REMOVE(b);
-                        H2_BLIST_INSERT_TAIL(&beam->purge, b);
+                        H2_BLIST_INSERT_TAIL(&beam->purge_list, b);
                     }
                     else {
                         /* another data bucket before this one in hold. this
@@ -334,50 +428,28 @@ static void h2_beam_emitted(h2_bucket_be
                     }
                 }
                 
-                proxy->bred = NULL;
+                proxy->bsender = NULL;
             }
             else {
                 /* it should be there unless we screwed up */
-                ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, beam->red_pool, 
+                ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, beam->send_pool, 
                               APLOGNO(03384) "h2_beam(%d-%s): emitted bucket not "
                               "in hold, n=%d", beam->id, beam->tag, 
                               (int)proxy->n);
-                AP_DEBUG_ASSERT(!proxy->bred);
+                ap_assert(!proxy->bsender);
             }
         }
         /* notify anyone waiting on space to become available */
         if (!bl.mutex) {
-            r_purge_reds(beam);
+            r_purge_sent(beam);
         }
-        else if (beam->m_cond) {
-            apr_thread_cond_broadcast(beam->m_cond);
+        else {
+            apr_thread_cond_broadcast(beam->change);
         }
         leave_yellow(beam, &bl);
     }
 }
 
-static void report_consumption(h2_bucket_beam *beam, int force)
-{
-    if (force || beam->received_bytes != beam->reported_consumed_bytes) {
-        if (beam->consumed_fn) { 
-            beam->consumed_fn(beam->consumed_ctx, beam, beam->received_bytes
-                              - beam->reported_consumed_bytes);
-        }
-        beam->reported_consumed_bytes = beam->received_bytes;
-    }
-}
-
-static void report_production(h2_bucket_beam *beam, int force)
-{
-    if (force || beam->sent_bytes != beam->reported_produced_bytes) {
-        if (beam->produced_fn) { 
-            beam->produced_fn(beam->produced_ctx, beam, beam->sent_bytes
-                              - beam->reported_produced_bytes);
-        }
-        beam->reported_produced_bytes = beam->sent_bytes;
-    }
-}
-
 static void h2_blist_cleanup(h2_blist *bl)
 {
     apr_bucket *e;
@@ -392,64 +464,188 @@ static apr_status_t beam_close(h2_bucket
 {
     if (!beam->closed) {
         beam->closed = 1;
-        if (beam->m_cond) {
-            apr_thread_cond_broadcast(beam->m_cond);
-        }
+        apr_thread_cond_broadcast(beam->change);
     }
     return APR_SUCCESS;
 }
 
-static apr_status_t beam_cleanup(void *data)
+int h2_beam_is_closed(h2_bucket_beam *beam)
+{
+    return beam->closed;
+}
+
+static int pool_register(h2_bucket_beam *beam, apr_pool_t *pool, 
+                         apr_status_t (*cleanup)(void *))
+{
+    if (pool && pool != beam->pool) {
+        apr_pool_pre_cleanup_register(pool, beam, cleanup);
+        return 1;
+    }
+    return 0;
+}
+
+static int pool_kill(h2_bucket_beam *beam, apr_pool_t *pool,
+                     apr_status_t (*cleanup)(void *)) {
+    if (pool && pool != beam->pool) {
+        apr_pool_cleanup_kill(pool, beam, cleanup);
+        return 1;
+    }
+    return 0;
+}
+
+static apr_status_t beam_recv_cleanup(void *data)
 {
     h2_bucket_beam *beam = data;
-    
-    beam_close(beam);
-    r_purge_reds(beam);
-    h2_blist_cleanup(&beam->red);
-    report_consumption(beam, 0);
+    /* receiver pool has gone away, clear references */
+    beam->recv_buffer = NULL;
+    beam->recv_pool = NULL;
+    return APR_SUCCESS;
+}
+
+static apr_status_t beam_send_cleanup(void *data)
+{
+    h2_bucket_beam *beam = data;
+    /* sender is going away, clear up all references to its memory */
+    r_purge_sent(beam);
+    h2_blist_cleanup(&beam->send_list);
+    report_consumption(beam, NULL);
     while (!H2_BPROXY_LIST_EMPTY(&beam->proxies)) {
         h2_beam_proxy *proxy = H2_BPROXY_LIST_FIRST(&beam->proxies);
         H2_BPROXY_REMOVE(proxy);
         proxy->beam = NULL;
-        proxy->bred = NULL;
+        proxy->bsender = NULL;
     }
-    h2_blist_cleanup(&beam->purge);
-    h2_blist_cleanup(&beam->hold);
-    
+    h2_blist_cleanup(&beam->purge_list);
+    h2_blist_cleanup(&beam->hold_list);
+    beam->send_pool = NULL;
     return APR_SUCCESS;
 }
 
+static void beam_set_send_pool(h2_bucket_beam *beam, apr_pool_t *pool) 
+{
+    if (beam->send_pool != pool) {
+        if (beam->send_pool && beam->send_pool != beam->pool) {
+            pool_kill(beam, beam->send_pool, beam_send_cleanup);
+            beam_send_cleanup(beam);
+        }
+        beam->send_pool = pool;
+        pool_register(beam, beam->send_pool, beam_send_cleanup);
+    }
+}
+
+static void recv_buffer_cleanup(h2_bucket_beam *beam, h2_beam_lock *bl)
+{
+    if (beam->recv_buffer && !APR_BRIGADE_EMPTY(beam->recv_buffer)) {
+        apr_bucket_brigade *bb = beam->recv_buffer;
+        apr_off_t bblen = 0;
+        
+        beam->recv_buffer = NULL;
+        apr_brigade_length(bb, 0, &bblen);
+        beam->received_bytes += bblen;
+        
+        /* need to do this unlocked since bucket destroy might 
+         * call this beam again. */
+        if (bl) leave_yellow(beam, bl);
+        apr_brigade_destroy(bb);
+        if (bl) enter_yellow(beam, bl);
+        
+        if (beam->cons_ev_cb) { 
+            beam->cons_ev_cb(beam->cons_ctx, beam);
+        }
+    }
+}
+
+static apr_status_t beam_cleanup(void *data)
+{
+    h2_bucket_beam *beam = data;
+    apr_status_t status = APR_SUCCESS;
+    int safe_send = (beam->owner == H2_BEAM_OWNER_SEND);
+    int safe_recv = (beam->owner == H2_BEAM_OWNER_RECV);
+    
+    /* 
+     * Owner of the beam is going away, depending on which side it owns,
+     * cleanup strategies will differ.
+     *
+     * In general, receiver holds references to memory from sender. 
+     * Clean up receiver first, if safe, then cleanup sender, if safe.
+     */
+     
+    /* When modify send is not safe, this means we still have multi-thread
+     * protection and the owner is receiving the buckets. If the sending
+     * side has not gone away, this means we could have dangling buckets
+     * in our lists that never get destroyed. This should not happen. */
+    ap_assert(safe_send || !beam->send_pool);
+    if (!H2_BLIST_EMPTY(&beam->send_list)) {
+        ap_assert(beam->send_pool);
+    }
+    
+    if (safe_recv) {
+        if (beam->recv_pool) {
+            pool_kill(beam, beam->recv_pool, beam_recv_cleanup);
+            beam->recv_pool = NULL;
+        }
+        recv_buffer_cleanup(beam, NULL);
+    }
+    else {
+        beam->recv_buffer = NULL;
+        beam->recv_pool = NULL;
+    }
+    
+    if (safe_send && beam->send_pool) {
+        pool_kill(beam, beam->send_pool, beam_send_cleanup);
+        status = beam_send_cleanup(beam);
+    }
+    
+    if (safe_recv) {
+        ap_assert(H2_BPROXY_LIST_EMPTY(&beam->proxies));
+        ap_assert(H2_BLIST_EMPTY(&beam->send_list));
+        ap_assert(H2_BLIST_EMPTY(&beam->hold_list));
+        ap_assert(H2_BLIST_EMPTY(&beam->purge_list));
+    }
+    return status;
+}
+
 apr_status_t h2_beam_destroy(h2_bucket_beam *beam)
 {
-    apr_pool_cleanup_kill(beam->red_pool, beam, beam_cleanup);
+    apr_pool_cleanup_kill(beam->pool, beam, beam_cleanup);
     return beam_cleanup(beam);
 }
 
-apr_status_t h2_beam_create(h2_bucket_beam **pbeam, apr_pool_t *red_pool, 
+apr_status_t h2_beam_create(h2_bucket_beam **pbeam, apr_pool_t *pool, 
                             int id, const char *tag, 
-                            apr_size_t max_buf_size)
+                            h2_beam_owner_t owner,
+                            apr_size_t max_buf_size,
+                            apr_interval_time_t timeout)
 {
     h2_bucket_beam *beam;
-    apr_status_t status = APR_SUCCESS;
+    apr_status_t rv = APR_SUCCESS;
     
-    beam = apr_pcalloc(red_pool, sizeof(*beam));
+    beam = apr_pcalloc(pool, sizeof(*beam));
     if (!beam) {
         return APR_ENOMEM;
     }
 
     beam->id = id;
     beam->tag = tag;
-    H2_BLIST_INIT(&beam->red);
-    H2_BLIST_INIT(&beam->hold);
-    H2_BLIST_INIT(&beam->purge);
+    beam->pool = pool;
+    beam->owner = owner;
+    H2_BLIST_INIT(&beam->send_list);
+    H2_BLIST_INIT(&beam->hold_list);
+    H2_BLIST_INIT(&beam->purge_list);
     H2_BPROXY_LIST_INIT(&beam->proxies);
-    beam->red_pool = red_pool;
+    beam->tx_mem_limits = 1;
     beam->max_buf_size = max_buf_size;
+    beam->timeout = timeout;
 
-    apr_pool_pre_cleanup_register(red_pool, beam, beam_cleanup);
-    *pbeam = beam;
-    
-    return status;
+    rv = apr_thread_mutex_create(&beam->lock, APR_THREAD_MUTEX_DEFAULT, pool);
+    if (APR_SUCCESS == rv) {
+        rv = apr_thread_cond_create(&beam->change, pool);
+        if (APR_SUCCESS == rv) {
+            apr_pool_pre_cleanup_register(pool, beam, beam_cleanup);
+            *pbeam = beam;
+        }
+    }
+    return rv;
 }
 
 void h2_beam_buffer_size_set(h2_bucket_beam *beam, apr_size_t buffer_size)
@@ -474,21 +670,6 @@ apr_size_t h2_beam_buffer_size_get(h2_bu
     return buffer_size;
 }
 
-void h2_beam_mutex_set(h2_bucket_beam *beam, 
-                       h2_beam_mutex_enter m_enter,
-                       apr_thread_cond_t *cond,
-                       void *m_ctx)
-{
-    h2_beam_lock bl;
-    
-    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        beam->m_enter = m_enter;
-        beam->m_ctx   = m_ctx;
-        beam->m_cond  = cond;
-        leave_yellow(beam, &bl);
-    }
-}
-
 void h2_beam_timeout_set(h2_bucket_beam *beam, apr_interval_time_t timeout)
 {
     h2_beam_lock bl;
@@ -516,13 +697,13 @@ void h2_beam_abort(h2_bucket_beam *beam)
     h2_beam_lock bl;
     
     if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        r_purge_reds(beam);
-        h2_blist_cleanup(&beam->red);
-        beam->aborted = 1;
-        report_consumption(beam, 0);
-        if (beam->m_cond) {
-            apr_thread_cond_broadcast(beam->m_cond);
+        if (!beam->aborted) {
+            beam->aborted = 1;
+            r_purge_sent(beam);
+            h2_blist_cleanup(&beam->send_list);
+            report_consumption(beam, &bl);
         }
+        apr_thread_cond_broadcast(beam->change);
         leave_yellow(beam, &bl);
     }
 }
@@ -532,140 +713,145 @@ apr_status_t h2_beam_close(h2_bucket_bea
     h2_beam_lock bl;
     
     if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        r_purge_reds(beam);
+        r_purge_sent(beam);
         beam_close(beam);
-        report_consumption(beam, 0);
+        report_consumption(beam, &bl);
         leave_yellow(beam, &bl);
     }
     return beam->aborted? APR_ECONNABORTED : APR_SUCCESS;
 }
 
-apr_status_t h2_beam_shutdown(h2_bucket_beam *beam, apr_read_type_e block,
-                              int clear_buffers)
+apr_status_t h2_beam_leave(h2_bucket_beam *beam)
+{
+    h2_beam_lock bl;
+    
+    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
+        recv_buffer_cleanup(beam, &bl);
+        beam->aborted = 1;
+        beam_close(beam);
+        leave_yellow(beam, &bl);
+    }
+    return APR_SUCCESS;
+}
+
+apr_status_t h2_beam_wait_empty(h2_bucket_beam *beam, apr_read_type_e block)
 {
     apr_status_t status;
     h2_beam_lock bl;
     
     if ((status = enter_yellow(beam, &bl)) == APR_SUCCESS) {
-        if (clear_buffers) {
-            r_purge_reds(beam);
-            h2_blist_cleanup(&beam->red);
-        }
-        beam_close(beam);
-        
-        while (status == APR_SUCCESS 
-               && (!H2_BPROXY_LIST_EMPTY(&beam->proxies)
-                   || (beam->green && !APR_BRIGADE_EMPTY(beam->green)))) {
-            if (block == APR_NONBLOCK_READ || !bl.mutex) {
-                status = APR_EAGAIN;
-                break;
-            }
-            if (beam->m_cond) {
-                apr_thread_cond_broadcast(beam->m_cond);
-            }
-            status = wait_cond(beam, bl.mutex);
-        }
+        status = wait_empty(beam, block, bl.mutex);
         leave_yellow(beam, &bl);
     }
     return status;
 }
 
+static void move_to_hold(h2_bucket_beam *beam, 
+                         apr_bucket_brigade *sender_bb)
+{
+    apr_bucket *b;
+    while (sender_bb && !APR_BRIGADE_EMPTY(sender_bb)) {
+        b = APR_BRIGADE_FIRST(sender_bb);
+        APR_BUCKET_REMOVE(b);
+        H2_BLIST_INSERT_TAIL(&beam->send_list, b);
+    }
+}
+
 static apr_status_t append_bucket(h2_bucket_beam *beam, 
-                                  apr_bucket *bred,
+                                  apr_bucket *b,
                                   apr_read_type_e block,
-                                  apr_pool_t *pool,
+                                  apr_size_t *pspace_left,
                                   h2_beam_lock *pbl)
 {
     const char *data;
     apr_size_t len;
-    apr_off_t space_left = 0;
     apr_status_t status;
+    int can_beam, check_len;
+    
+    if (beam->aborted) {
+        return APR_ECONNABORTED;
+    }
     
-    if (APR_BUCKET_IS_METADATA(bred)) {
-        if (APR_BUCKET_IS_EOS(bred)) {
+    if (APR_BUCKET_IS_METADATA(b)) {
+        if (APR_BUCKET_IS_EOS(b)) {
             beam->closed = 1;
         }
-        APR_BUCKET_REMOVE(bred);
-        H2_BLIST_INSERT_TAIL(&beam->red, bred);
+        APR_BUCKET_REMOVE(b);
+        H2_BLIST_INSERT_TAIL(&beam->send_list, b);
         return APR_SUCCESS;
     }
-    else if (APR_BUCKET_IS_FILE(bred)) {
-        /* file bucket lengths do not really count */
+    else if (APR_BUCKET_IS_FILE(b)) {
+        /* For file buckets the problem is their internal readpool that
+         * is used on the first read to allocate buffer/mmap.
+         * Since setting aside a file bucket will de-register the
+         * file cleanup function from the previous pool, we need to
+         * call that only from the sender thread.
+         *
+         * Currently, we do not handle file bucket with refcount > 1 as
+         * the beam is then not in complete control of the file's lifetime.
+         * Which results in the bug that a file get closed by the receiver
+         * while the sender or the beam still have buckets using it. 
+         * 
+         * Additionally, we allow callbacks to prevent beaming file
+         * handles across. The use case for this is to limit the number 
+         * of open file handles and rather use a less efficient beam
+         * transport. */
+        apr_bucket_file *bf = b->data;
+        apr_file_t *fd = bf->fd;
+        can_beam = (bf->refcount.refcount == 1);
+        if (can_beam && beam->can_beam_fn) {
+            can_beam = beam->can_beam_fn(beam->can_beam_ctx, beam, fd);
+        }
+        check_len = !can_beam;
     }
     else {
-        space_left = calc_space_left(beam);
-        if (space_left > 0 && bred->length == ((apr_size_t)-1)) {
+        if (b->length == ((apr_size_t)-1)) {
             const char *data;
-            status = apr_bucket_read(bred, &data, &len, APR_BLOCK_READ);
+            status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
             if (status != APR_SUCCESS) {
                 return status;
             }
         }
-        
-        if (space_left < bred->length) {
-            status = r_wait_space(beam, block, pbl, &space_left);
-            if (status != APR_SUCCESS) {
-                return status;
-            }
-            if (space_left <= 0) {
-                return APR_EAGAIN;
-            }
-        }
-        /* space available, maybe need bucket split */
+        check_len = 1;
     }
     
+    if (check_len) {
+        if (b->length > *pspace_left) {
+            apr_bucket_split(b, *pspace_left);
+        }
+        *pspace_left -= b->length;
+    }
 
-    /* The fundamental problem is that reading a red bucket from
-     * a green thread is a total NO GO, because the bucket might use
+    /* The fundamental problem is that reading a sender bucket from
+     * a receiver thread is a total NO GO, because the bucket might use
      * its pool/bucket_alloc from a foreign thread and that will
      * corrupt. */
     status = APR_ENOTIMPL;
-    if (beam->closed && bred->length > 0) {
-        status = APR_EOF;
-    }
-    else if (APR_BUCKET_IS_TRANSIENT(bred)) {
+    if (APR_BUCKET_IS_TRANSIENT(b)) {
         /* this takes care of transient buckets and converts them
          * into heap ones. Other bucket types might or might not be
          * affected by this. */
-        status = apr_bucket_setaside(bred, pool);
+        status = apr_bucket_setaside(b, beam->send_pool);
     }
-    else if (APR_BUCKET_IS_HEAP(bred)) {
-        /* For heap buckets read from a green thread is fine. The
+    else if (APR_BUCKET_IS_HEAP(b)) {
+        /* For heap buckets read from a receiver thread is fine. The
          * data will be there and live until the bucket itself is
          * destroyed. */
         status = APR_SUCCESS;
     }
-    else if (APR_BUCKET_IS_POOL(bred)) {
+    else if (APR_BUCKET_IS_POOL(b)) {
         /* pool buckets are bastards that register at pool cleanup
          * to morph themselves into heap buckets. That may happen anytime,
          * even after the bucket data pointer has been read. So at
-         * any time inside the green thread, the pool bucket memory
+         * any time inside the receiver thread, the pool bucket memory
          * may disappear. yikes. */
-        status = apr_bucket_read(bred, &data, &len, APR_BLOCK_READ);
+        status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
         if (status == APR_SUCCESS) {
-            apr_bucket_heap_make(bred, data, len, NULL);
+            apr_bucket_heap_make(b, data, len, NULL);
         }
     }
-    else if (APR_BUCKET_IS_FILE(bred)) {
-        /* For file buckets the problem is their internal readpool that
-         * is used on the first read to allocate buffer/mmap.
-         * Since setting aside a file bucket will de-register the
-         * file cleanup function from the previous pool, we need to
-         * call that from a red thread. 
-         * Additionally, we allow callbacks to prevent beaming file
-         * handles across. The use case for this is to limit the number 
-         * of open file handles and rather use a less efficient beam
-         * transport. */
-        apr_file_t *fd = ((apr_bucket_file *)bred->data)->fd;
-        int can_beam = 1;
-        if (beam->last_beamed != fd && beam->can_beam_fn) {
-            can_beam = beam->can_beam_fn(beam->can_beam_ctx, beam, fd);
-        }
-        if (can_beam) {
-            beam->last_beamed = fd;
-            status = apr_bucket_setaside(bred, pool);
-        }
-        /* else: enter ENOTIMPL case below */
+    else if (APR_BUCKET_IS_FILE(b) && can_beam) {
+        status = apr_bucket_setaside(b, beam->send_pool);
     }
     
     if (status == APR_ENOTIMPL) {
@@ -673,17 +859,11 @@ static apr_status_t append_bucket(h2_buc
          * but hope that after read, its data stays immutable for the
          * lifetime of the bucket. (see pool bucket handling above for
          * a counter example).
-         * We do the read while in a red thread, so that the bucket may
+         * We do the read while in the sender thread, so that the bucket may
          * use pools/allocators safely. */
-        if (space_left < APR_BUCKET_BUFF_SIZE) {
-            space_left = APR_BUCKET_BUFF_SIZE;
-        }
-        if (space_left < bred->length) {
-            apr_bucket_split(bred, space_left);
-        }
-        status = apr_bucket_read(bred, &data, &len, APR_BLOCK_READ);
+        status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
         if (status == APR_SUCCESS) {
-            status = apr_bucket_setaside(bred, pool);
+            status = apr_bucket_setaside(b, beam->send_pool);
         }
     }
     
@@ -691,44 +871,65 @@ static apr_status_t append_bucket(h2_buc
         return status;
     }
     
-    APR_BUCKET_REMOVE(bred);
-    H2_BLIST_INSERT_TAIL(&beam->red, bred);
-    beam->sent_bytes += bred->length;
-    
+    APR_BUCKET_REMOVE(b);
+    H2_BLIST_INSERT_TAIL(&beam->send_list, b);
+    beam->sent_bytes += b->length;
+
     return APR_SUCCESS;
 }
 
+void h2_beam_send_from(h2_bucket_beam *beam, apr_pool_t *p)
+{
+    h2_beam_lock bl;
+    /* Called from the sender thread to add buckets to the beam */
+    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
+        r_purge_sent(beam);
+        beam_set_send_pool(beam, p);
+        leave_yellow(beam, &bl);
+    }
+}
+
 apr_status_t h2_beam_send(h2_bucket_beam *beam, 
-                          apr_bucket_brigade *red_brigade, 
+                          apr_bucket_brigade *sender_bb, 
                           apr_read_type_e block)
 {
-    apr_bucket *bred;
-    apr_status_t status = APR_SUCCESS;
+    apr_bucket *b;
+    apr_status_t rv = APR_SUCCESS;
+    apr_size_t space_left = 0;
     h2_beam_lock bl;
 
-    /* Called from the red thread to add buckets to the beam */
+    /* Called from the sender thread to add buckets to the beam */
     if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        r_purge_reds(beam);
+        ap_assert(beam->send_pool);
+        r_purge_sent(beam);
         
         if (beam->aborted) {
-            status = APR_ECONNABORTED;
+            move_to_hold(beam, sender_bb);
+            rv = APR_ECONNABORTED;
         }
-        else if (red_brigade) {
-            int force_report = !APR_BRIGADE_EMPTY(red_brigade); 
-            while (!APR_BRIGADE_EMPTY(red_brigade)
-                   && status == APR_SUCCESS) {
-                bred = APR_BRIGADE_FIRST(red_brigade);
-                status = append_bucket(beam, bred, block, beam->red_pool, &bl);
-            }
-            report_production(beam, force_report);
-            if (beam->m_cond) {
-                apr_thread_cond_broadcast(beam->m_cond);
+        else if (sender_bb) {
+            int force_report = !APR_BRIGADE_EMPTY(sender_bb);
+            
+            space_left = calc_space_left(beam);
+            while (!APR_BRIGADE_EMPTY(sender_bb) && APR_SUCCESS == rv) {
+                if (space_left <= 0) {
+                    report_prod_io(beam, force_report, &bl);
+                    rv = wait_not_full(beam, block, &space_left, &bl);
+                    if (APR_SUCCESS != rv) {
+                        break;
+                    }
+                }
+                b = APR_BRIGADE_FIRST(sender_bb);
+                rv = append_bucket(beam, b, block, &space_left, &bl);
             }
+            
+            report_prod_io(beam, force_report, &bl);
+            apr_thread_cond_broadcast(beam->change);
         }
-        report_consumption(beam, 0);
+        report_consumption(beam, &bl);
         leave_yellow(beam, &bl);
     }
-    return status;
+    return rv;
 }
 
 apr_status_t h2_beam_receive(h2_bucket_beam *beam, 
@@ -737,63 +938,75 @@ apr_status_t h2_beam_receive(h2_bucket_b
                              apr_off_t readbytes)
 {
     h2_beam_lock bl;
-    apr_bucket *bred, *bgreen, *ng;
+    apr_bucket *bsender, *brecv, *ng;
     int transferred = 0;
     apr_status_t status = APR_SUCCESS;
-    apr_off_t remain = readbytes;
+    apr_off_t remain;
+    int transferred_buckets = 0;
     
-    /* Called from the green thread to take buckets from the beam */
+    /* Called from the receiver thread to take buckets from the beam */
     if (enter_yellow(beam, &bl) == APR_SUCCESS) {
+        if (readbytes <= 0) {
+            readbytes = APR_SIZE_MAX;
+        }
+        remain = readbytes;
+        
 transfer:
         if (beam->aborted) {
-            if (beam->green && !APR_BRIGADE_EMPTY(beam->green)) {
-                apr_brigade_cleanup(beam->green);
-            }
+            recv_buffer_cleanup(beam, &bl);
             status = APR_ECONNABORTED;
             goto leave;
         }
 
-        /* transfer enough buckets from our green brigade, if we have one */
-        while (beam->green
-               && !APR_BRIGADE_EMPTY(beam->green)
-               && (readbytes <= 0 || remain >= 0)) {
-            bgreen = APR_BRIGADE_FIRST(beam->green);
-            if (readbytes > 0 && bgreen->length > 0 && remain <= 0) {
+        /* transfer enough buckets from our receiver brigade, if we have one */
+        while (remain >= 0 
+               && beam->recv_buffer 
+               && !APR_BRIGADE_EMPTY(beam->recv_buffer)) {
+               
+            brecv = APR_BRIGADE_FIRST(beam->recv_buffer);
+            if (brecv->length > 0 && remain <= 0) {
                 break;
             }            
-            APR_BUCKET_REMOVE(bgreen);
-            APR_BRIGADE_INSERT_TAIL(bb, bgreen);
-            remain -= bgreen->length;
+            APR_BUCKET_REMOVE(brecv);
+            APR_BRIGADE_INSERT_TAIL(bb, brecv);
+            remain -= brecv->length;
             ++transferred;
         }
 
-        /* transfer from our red brigade, transforming red buckets to
-         * green ones until we have enough */
-        while (!H2_BLIST_EMPTY(&beam->red) && (readbytes <= 0 || remain >= 0)) {
-            bred = H2_BLIST_FIRST(&beam->red);
-            bgreen = NULL;
-            
-            if (readbytes > 0 && bred->length > 0 && remain <= 0) {
+        /* transfer from our sender brigade, transforming sender buckets to
+         * receiver ones until we have enough */
+        while (remain >= 0 && !H2_BLIST_EMPTY(&beam->send_list)) {
+               
+            brecv = NULL;
+            bsender = H2_BLIST_FIRST(&beam->send_list);            
+            if (bsender->length > 0 && remain <= 0) {
                 break;
             }
                         
-            if (APR_BUCKET_IS_METADATA(bred)) {
-                if (APR_BUCKET_IS_EOS(bred)) {
-                    bgreen = apr_bucket_eos_create(bb->bucket_alloc);
+            if (APR_BUCKET_IS_METADATA(bsender)) {
+                if (APR_BUCKET_IS_EOS(bsender)) {
+                    brecv = apr_bucket_eos_create(bb->bucket_alloc);
                     beam->close_sent = 1;
                 }
-                else if (APR_BUCKET_IS_FLUSH(bred)) {
-                    bgreen = apr_bucket_flush_create(bb->bucket_alloc);
+                else if (APR_BUCKET_IS_FLUSH(bsender)) {
+                    brecv = apr_bucket_flush_create(bb->bucket_alloc);
                 }
-                else {
-                    /* put red into hold, no green sent out */
+                else if (AP_BUCKET_IS_ERROR(bsender)) {
+                    ap_bucket_error *eb = (ap_bucket_error *)bsender;
+                    brecv = ap_bucket_error_create(eb->status, eb->data,
+                                                    bb->p, bb->bucket_alloc);
                 }
             }
-            else if (APR_BUCKET_IS_FILE(bred)) {
+            else if (bsender->length == 0) {
+                APR_BUCKET_REMOVE(bsender);
+                H2_BLIST_INSERT_TAIL(&beam->hold_list, bsender);
+                continue;
+            }
+            else if (APR_BUCKET_IS_FILE(bsender)) {
                 /* This is set aside into the target brigade pool so that 
                  * any read operation messes with that pool and not 
-                 * the red one. */
-                apr_bucket_file *f = (apr_bucket_file *)bred->data;
+                 * the sender one. */
+                apr_bucket_file *f = (apr_bucket_file *)bsender->data;
                 apr_file_t *fd = f->fd;
                 int setaside = (f->readpool != bb->p);
                 
@@ -804,7 +1017,7 @@ transfer:
                     }
                     ++beam->files_beamed;
                 }
-                ng = apr_brigade_insert_file(bb, fd, bred->start, bred->length, 
+                ng = apr_brigade_insert_file(bb, fd, bsender->start, bsender->length, 
                                              bb->p);
 #if APR_HAS_MMAP
                 /* disable mmap handling as this leads to segfaults when
@@ -812,55 +1025,70 @@ transfer:
                  * been handed out. See also PR 59348 */
                 apr_bucket_file_enable_mmap(ng, 0);
 #endif
-                remain -= bred->length;
-                ++transferred;
-                APR_BUCKET_REMOVE(bred);
-                H2_BLIST_INSERT_TAIL(&beam->hold, bred);
+                APR_BUCKET_REMOVE(bsender);
+                H2_BLIST_INSERT_TAIL(&beam->hold_list, bsender);
+
+                remain -= bsender->length;
                 ++transferred;
+                ++transferred_buckets;
                 continue;
             }
             else {
-                /* create a "green" standin bucket. we took care about the
-                 * underlying red bucket and its data when we placed it into
-                 * the red brigade.
-                 * the beam bucket will notify us on destruction that bred is
+                /* create a "receiver" standin bucket. we took care about the
+                 * underlying sender bucket and its data when we placed it into
+                 * the sender brigade.
+                 * the beam bucket will notify us on destruction that bsender is
                  * no longer needed. */
-                bgreen = h2_beam_bucket_create(beam, bred, bb->bucket_alloc,
+                brecv = h2_beam_bucket_create(beam, bsender, bb->bucket_alloc,
                                                beam->buckets_sent++);
             }
             
-            /* Place the red bucket into our hold, to be destroyed when no
-             * green bucket references it any more. */
-            APR_BUCKET_REMOVE(bred);
-            H2_BLIST_INSERT_TAIL(&beam->hold, bred);
-            beam->received_bytes += bred->length;
-            if (bgreen) {
-                APR_BRIGADE_INSERT_TAIL(bb, bgreen);
-                remain -= bgreen->length;
+            /* Place the sender bucket into our hold, to be destroyed when no
+             * receiver bucket references it any more. */
+            APR_BUCKET_REMOVE(bsender);
+            H2_BLIST_INSERT_TAIL(&beam->hold_list, bsender);
+            
+            beam->received_bytes += bsender->length;
+            ++transferred_buckets;
+            
+            if (brecv) {
+                APR_BRIGADE_INSERT_TAIL(bb, brecv);
+                remain -= brecv->length;
                 ++transferred;
             }
+            else {
+                /* let outside hook determine how bucket is beamed */
+                leave_yellow(beam, &bl);
+                brecv = h2_beam_bucket(beam, bb, bsender);
+                enter_yellow(beam, &bl);
+                
+                while (brecv && brecv != APR_BRIGADE_SENTINEL(bb)) {
+                    ++transferred;
+                    remain -= brecv->length;
+                    brecv = APR_BUCKET_NEXT(brecv);
+                }
+            }
         }
 
-        if (readbytes > 0 && remain < 0) {
-            /* too much, put some back */
+        if (remain < 0) {
+            /* too much, put some back into out recv_buffer */
             remain = readbytes;
-            for (bgreen = APR_BRIGADE_FIRST(bb);
-                 bgreen != APR_BRIGADE_SENTINEL(bb);
-                 bgreen = APR_BUCKET_NEXT(bgreen)) {
-                 remain -= bgreen->length;
-                 if (remain < 0) {
-                     apr_bucket_split(bgreen, bgreen->length+remain);
-                     beam->green = apr_brigade_split_ex(bb, 
-                                                        APR_BUCKET_NEXT(bgreen), 
-                                                        beam->green);
-                     break;
-                 }
+            for (brecv = APR_BRIGADE_FIRST(bb);
+                 brecv != APR_BRIGADE_SENTINEL(bb);
+                 brecv = APR_BUCKET_NEXT(brecv)) {
+                remain -= (beam->tx_mem_limits? bucket_mem_used(brecv) 
+                           : brecv->length);
+                if (remain < 0) {
+                    apr_bucket_split(brecv, brecv->length+remain);
+                    beam->recv_buffer = apr_brigade_split_ex(bb, 
+                                                             APR_BUCKET_NEXT(brecv), 
+                                                             beam->recv_buffer);
+                    break;
+                }
             }
         }
 
-        if (beam->closed 
-            && (!beam->green || APR_BRIGADE_EMPTY(beam->green))
-            && H2_BLIST_EMPTY(&beam->red)) {
+        if (beam->closed && buffer_is_empty(beam)) {
             /* beam is closed and we have nothing more to receive */ 
             if (!beam->close_sent) {
                 apr_bucket *b = apr_bucket_eos_create(bb->bucket_alloc);
@@ -871,22 +1099,23 @@ transfer:
             }
         }
         
+        if (transferred_buckets > 0) {
+           if (beam->cons_ev_cb) { 
+               beam->cons_ev_cb(beam->cons_ctx, beam);
+            }
+        }
+        
         if (transferred) {
+            apr_thread_cond_broadcast(beam->change);
             status = APR_SUCCESS;
         }
-        else if (beam->closed) {
-            status = APR_EOF;
-        }
-        else if (block == APR_BLOCK_READ && bl.mutex && beam->m_cond) {
-            status = wait_cond(beam, bl.mutex);
+        else {
+            status = wait_not_empty(beam, block, bl.mutex);
             if (status != APR_SUCCESS) {
                 goto leave;
             }
             goto transfer;
         }
-        else {
-            status = APR_EAGAIN;
-        }
 leave:        
         leave_yellow(beam, &bl);
     }
@@ -894,25 +1123,25 @@ leave:
 }
 
 void h2_beam_on_consumed(h2_bucket_beam *beam, 
-                         h2_beam_io_callback *cb, void *ctx)
+                         h2_beam_ev_callback *ev_cb,
+                         h2_beam_io_callback *io_cb, void *ctx)
 {
     h2_beam_lock bl;
-    
     if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        beam->consumed_fn = cb;
-        beam->consumed_ctx = ctx;
+        beam->cons_ev_cb = ev_cb;
+        beam->cons_io_cb = io_cb;
+        beam->cons_ctx = ctx;
         leave_yellow(beam, &bl);
     }
 }
 
 void h2_beam_on_produced(h2_bucket_beam *beam, 
-                         h2_beam_io_callback *cb, void *ctx)
+                         h2_beam_io_callback *io_cb, void *ctx)
 {
     h2_beam_lock bl;
-    
     if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        beam->produced_fn = cb;
-        beam->produced_ctx = ctx;
+        beam->prod_io_cb = io_cb;
+        beam->prod_ctx = ctx;
         leave_yellow(beam, &bl);
     }
 }
@@ -937,8 +1166,8 @@ apr_off_t h2_beam_get_buffered(h2_bucket
     h2_beam_lock bl;
     
     if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        for (b = H2_BLIST_FIRST(&beam->red); 
-            b != H2_BLIST_SENTINEL(&beam->red);
+        for (b = H2_BLIST_FIRST(&beam->send_list); 
+            b != H2_BLIST_SENTINEL(&beam->send_list);
             b = APR_BUCKET_NEXT(b)) {
             /* should all have determinate length */
             l += b->length;
@@ -955,16 +1184,10 @@ apr_off_t h2_beam_get_mem_used(h2_bucket
     h2_beam_lock bl;
     
     if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        for (b = H2_BLIST_FIRST(&beam->red); 
-            b != H2_BLIST_SENTINEL(&beam->red);
+        for (b = H2_BLIST_FIRST(&beam->send_list); 
+            b != H2_BLIST_SENTINEL(&beam->send_list);
             b = APR_BUCKET_NEXT(b)) {
-            if (APR_BUCKET_IS_FILE(b)) {
-                /* do not count */
-            }
-            else {
-                /* should all have determinate length */
-                l += b->length;
-            }
+            l += bucket_mem_used(b);
         }
         leave_yellow(beam, &bl);
     }
@@ -977,16 +1200,23 @@ int h2_beam_empty(h2_bucket_beam *beam)
     h2_beam_lock bl;
     
     if (enter_yellow(beam, &bl) == APR_SUCCESS) {
-        empty = (H2_BLIST_EMPTY(&beam->red) 
-                 && (!beam->green || APR_BRIGADE_EMPTY(beam->green)));
+        empty = (H2_BLIST_EMPTY(&beam->send_list) 
+                 && (!beam->recv_buffer || APR_BRIGADE_EMPTY(beam->recv_buffer)));
         leave_yellow(beam, &bl);
     }
     return empty;
 }
 
-int h2_beam_closed(h2_bucket_beam *beam)
+int h2_beam_holds_proxies(h2_bucket_beam *beam)
 {
-    return beam->closed;
+    int has_proxies = 1;
+    h2_beam_lock bl;
+    
+    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
+        has_proxies = !H2_BPROXY_LIST_EMPTY(&beam->proxies);
+        leave_yellow(beam, &bl);
+    }
+    return has_proxies;
 }
 
 int h2_beam_was_received(h2_bucket_beam *beam)
@@ -1013,3 +1243,31 @@ apr_size_t h2_beam_get_files_beamed(h2_b
     return n;
 }
 
+int h2_beam_no_files(void *ctx, h2_bucket_beam *beam, apr_file_t *file)
+{
+    return 0;
+}
+
+int h2_beam_report_consumption(h2_bucket_beam *beam)
+{
+    h2_beam_lock bl;
+    int rv = 0;
+    if (enter_yellow(beam, &bl) == APR_SUCCESS) {
+        rv = report_consumption(beam, &bl);
+        leave_yellow(beam, &bl);
+    }
+    return rv;
+}
+
+void h2_beam_log(h2_bucket_beam *beam, conn_rec *c, int level, const char *msg)
+{
+    if (beam && APLOG_C_IS_LEVEL(c,level)) {
+        ap_log_cerror(APLOG_MARK, level, 0, c, 
+                      "beam(%ld-%d,%s,closed=%d,aborted=%d,empty=%d,buf=%ld): %s", 
+                      (c->master? c->master->id : c->id), beam->id, beam->tag, 
+                      beam->closed, beam->aborted, h2_beam_empty(beam), 
+                      (long)h2_beam_get_buffered(beam), msg);
+    }
+}
+
+
diff -up --new-file httpd-2.4.23/modules/http2/h2_bucket_beam.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_bucket_beam.h
--- httpd-2.4.23/modules/http2/h2_bucket_beam.h	2016-06-09 12:38:10.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_bucket_beam.h	2017-10-13 10:37:45.000000000 +0200
@@ -51,19 +51,6 @@ typedef struct {
         APR_RING_PREPEND(&(a)->list, &(b)->list, apr_bucket, link);	\
     } while (0)
 
-/**
- * Print the buckets in the list into the buffer (type and lengths).
- * @param buffer the buffer to print into
- * @param bmax max number of characters to place in buffer, incl. trailing 0
- * @param tag tag string for this bucket list
- * @param sep separator to use
- * @param bl the bucket list to print
- * @return number of characters printed
- */
-apr_size_t h2_util_bl_print(char *buffer, apr_size_t bmax, 
-                            const char *tag, const char *sep, 
-                            h2_blist *bl);
-
 /*******************************************************************************
  * h2_bucket_beam
  ******************************************************************************/
@@ -72,27 +59,25 @@ apr_size_t h2_util_bl_print(char *buffer
  * A h2_bucket_beam solves the task of transferring buckets, esp. their data,
  * across threads with zero buffer copies.
  *
- * When a thread, let's call it the red thread, wants to send buckets to
+ * When a thread, let's call it the sender thread, wants to send buckets to
  * another, the green thread, it creates a h2_bucket_beam and adds buckets
  * via the h2_beam_send(). It gives the beam to the green thread which then
  * can receive buckets into its own brigade via h2_beam_receive().
  *
- * Sending and receiving can happen concurrently, if a thread mutex is set
- * for the beam, see h2_beam_mutex_set.
+ * Sending and receiving can happen concurrently.
  *
  * The beam can limit the amount of data it accepts via the buffer_size. This
- * can also be adjusted during its lifetime. When the beam not only gets a 
- * mutex but als a condition variable (in h2_beam_mutex_set()), sends and
- * receives can be done blocking. A timeout can be set for such blocks.
+ * can also be adjusted during its lifetime. Sends and receives can be done blocking. 
+ * A timeout can be set for such blocks.
  *
  * Care needs to be taken when terminating the beam. The beam registers at
  * the pool it was created with and will cleanup after itself. However, if
  * received buckets do still exist, already freed memory might be accessed.
- * The beam does a AP_DEBUG_ASSERT on this condition.
+ * The beam does a assertion on this condition.
  * 
  * The proper way of shutting down a beam is to first make sure there are no
  * more green buckets out there, then cleanup the beam to purge eventually
- * still existing red buckets and then, possibly, terminate the beam itself
+ * still existing sender buckets and then, possibly, terminate the beam itself
  * (or the pool it was created with).
  *
  * The following restrictions apply to bucket transport:
@@ -105,32 +90,32 @@ apr_size_t h2_util_bl_print(char *buffer
  *   - file buckets will transfer the file itself into a new bucket, if allowed
  *   - all other buckets are read on send to make sure data is present
  *
- * This assures that when the red thread sends its red buckets, the data
- * is made accessible while still on the red side. The red bucket then enters
+ * This assures that when the sender thread sends its sender buckets, the data
+ * is made accessible while still on the sender side. The sender bucket then enters
  * the beams hold storage.
- * When the green thread calls receive, red buckets in the hold are wrapped
+ * When the green thread calls receive, sender buckets in the hold are wrapped
  * into special beam buckets. Beam buckets on read present the data directly
- * from the internal red one, but otherwise live on the green side. When a
+ * from the internal sender one, but otherwise live on the green side. When a
  * beam bucket gets destroyed, it notifies its beam that the corresponding
- * red bucket from the hold may be destroyed.
+ * sender bucket from the hold may be destroyed.
  * Since the destruction of green buckets happens in the green thread, any
- * corresponding red bucket can not immediately be destroyed, as that would
+ * corresponding sender bucket can not immediately be destroyed, as that would
  * result in race conditions.
- * Instead, the beam transfers such red buckets from the hold to the purge
- * storage. Next time there is a call from the red side, the buckets in
+ * Instead, the beam transfers such sender buckets from the hold to the purge
+ * storage. Next time there is a call from the sender side, the buckets in
  * purge will be deleted.
  *
- * There are callbacks that can be registered with a beam:
- * - a "consumed" callback that gets called on the red side with the
+ * There are callbacks that can be registesender with a beam:
+ * - a "consumed" callback that gets called on the sender side with the
  *   amount of data that has been received by the green side. The amount
- *   is a delta from the last callback invocation. The red side can trigger
+ *   is a delta from the last callback invocation. The sender side can trigger
  *   these callbacks by calling h2_beam_send() with a NULL brigade.
  * - a "can_beam_file" callback that can prohibit the transfer of file handles
  *   through the beam. This will cause file buckets to be read on send and
  *   its data buffer will then be transports just like a heap bucket would.
  *   When no callback is registered, no restrictions apply and all files are
  *   passed through.
- *   File handles transferred to the green side will stay there until the
+ *   File handles transfersender to the green side will stay there until the
  *   receiving brigade's pool is destroyed/cleared. If the pool lives very
  *   long or if many different files are beamed, the process might run out
  *   of available file handles.
@@ -154,6 +139,7 @@ typedef apr_status_t h2_beam_mutex_enter
 
 typedef void h2_beam_io_callback(void *ctx, h2_bucket_beam *beam,
                                  apr_off_t bytes);
+typedef void h2_beam_ev_callback(void *ctx, h2_bucket_beam *beam);
 
 typedef struct h2_beam_proxy h2_beam_proxy;
 typedef struct {
@@ -163,15 +149,29 @@ typedef struct {
 typedef int h2_beam_can_beam_callback(void *ctx, h2_bucket_beam *beam,
                                       apr_file_t *file);
 
+typedef enum {
+    H2_BEAM_OWNER_SEND,
+    H2_BEAM_OWNER_RECV
+} h2_beam_owner_t;
+
+/**
+ * Will deny all transfer of apr_file_t across the beam and force
+ * a data copy instead.
+ */
+int h2_beam_no_files(void *ctx, h2_bucket_beam *beam, apr_file_t *file);
+
 struct h2_bucket_beam {
     int id;
     const char *tag;
-    h2_blist red;
-    h2_blist hold;
-    h2_blist purge;
-    apr_bucket_brigade *green;
+    apr_pool_t *pool;
+    h2_beam_owner_t owner;
+    h2_blist send_list;
+    h2_blist hold_list;
+    h2_blist purge_list;
+    apr_bucket_brigade *recv_buffer;
     h2_bproxy_list proxies;
-    apr_pool_t *red_pool;
+    apr_pool_t *send_pool;
+    apr_pool_t *recv_pool;
     
     apr_size_t max_buf_size;
     apr_interval_time_t timeout;
@@ -181,22 +181,24 @@ struct h2_bucket_beam {
 
     apr_size_t buckets_sent;  /* # of beam buckets sent */
     apr_size_t files_beamed;  /* how many file handles have been set aside */
-    apr_file_t *last_beamed;  /* last file beamed */
     
     unsigned int aborted : 1;
     unsigned int closed : 1;
     unsigned int close_sent : 1;
+    unsigned int tx_mem_limits : 1; /* only memory size counts on transfers */
 
-    void *m_ctx;
-    h2_beam_mutex_enter *m_enter;
-    struct apr_thread_cond_t *m_cond;
+    struct apr_thread_mutex_t *lock;
+    struct apr_thread_cond_t *change;
     
-    apr_off_t reported_consumed_bytes; /* amount of bytes reported as consumed */
-    h2_beam_io_callback *consumed_fn;
-    void *consumed_ctx;
-    apr_off_t reported_produced_bytes; /* amount of bytes reported as produced */
-    h2_beam_io_callback *produced_fn;
-    void *produced_ctx;
+    apr_off_t cons_bytes_reported;    /* amount of bytes reported as consumed */
+    h2_beam_ev_callback *cons_ev_cb;
+    h2_beam_io_callback *cons_io_cb;
+    void *cons_ctx;
+
+    apr_off_t prod_bytes_reported;    /* amount of bytes reported as produced */
+    h2_beam_io_callback *prod_io_cb;
+    void *prod_ctx;
+
     h2_beam_can_beam_callback *can_beam_fn;
     void *can_beam_ctx;
 };
@@ -208,22 +210,25 @@ struct h2_bucket_beam {
  * mutex and will be used in multiple threads. It needs a pool allocator
  * that is only used inside that same mutex.
  *
- * @param pbeam will hold the created beam on return
- * @param red_pool      pool usable on red side, beam lifeline
+ * @param pbeam         will hold the created beam on return
+ * @param pool          pool owning the beam, beam will cleanup when pool released
+ * @param id            identifier of the beam
+ * @param tag           tag identifying beam for logging
+ * @param owner         if the beam is owned by the sender or receiver, e.g. if
+ *                      the pool owner is using this beam for sending or receiving
  * @param buffer_size   maximum memory footprint of buckets buffered in beam, or
  *                      0 for no limitation
- *
- * Call from the red side only.
+ * @param timeout       timeout for blocking operations
  */
 apr_status_t h2_beam_create(h2_bucket_beam **pbeam,
-                            apr_pool_t *red_pool, 
-                            int id, const char *tag, 
-                            apr_size_t buffer_size);
+                            apr_pool_t *pool, 
+                            int id, const char *tag,
+                            h2_beam_owner_t owner,  
+                            apr_size_t buffer_size,
+                            apr_interval_time_t timeout);
 
 /**
  * Destroys the beam immediately without cleanup.
- *
- * Call from the red side only.
  */ 
 apr_status_t h2_beam_destroy(h2_bucket_beam *beam);
 
@@ -233,19 +238,26 @@ apr_status_t h2_beam_destroy(h2_bucket_b
  * All accepted buckets are removed from the given brigade. Will return with
  * APR_EAGAIN on non-blocking sends when not all buckets could be accepted.
  * 
- * Call from the red side only.
+ * Call from the sender side only.
  */
 apr_status_t h2_beam_send(h2_bucket_beam *beam,  
-                          apr_bucket_brigade *red_buckets, 
+                          apr_bucket_brigade *bb, 
                           apr_read_type_e block);
 
 /**
+ * Register the pool from which future buckets are send. This defines
+ * the lifetime of the buckets, e.g. the pool should not be cleared/destroyed
+ * until the data is no longer needed (or has been received).
+ */
+void h2_beam_send_from(h2_bucket_beam *beam, apr_pool_t *p);
+
+/**
  * Receive buckets from the beam into the given brigade. Will return APR_EOF
  * when reading past an EOS bucket. Reads can be blocking until data is 
  * available or the beam has been closed. Non-blocking calls return APR_EAGAIN
  * if no data is available.
  *
- * Call from the green side only.
+ * Call from the receiver side only.
  */
 apr_status_t h2_beam_receive(h2_bucket_beam *beam, 
                              apr_bucket_brigade *green_buckets, 
@@ -253,35 +265,41 @@ apr_status_t h2_beam_receive(h2_bucket_b
                              apr_off_t readbytes);
 
 /**
- * Determine if beam is closed. May still contain buffered data. 
- * 
- * Call from red or green side.
+ * Determine if beam is empty. 
  */
-int h2_beam_closed(h2_bucket_beam *beam);
+int h2_beam_empty(h2_bucket_beam *beam);
 
 /**
- * Determine if beam is empty. 
- * 
- * Call from red or green side.
+ * Determine if beam has handed out proxy buckets that are not destroyed. 
  */
-int h2_beam_empty(h2_bucket_beam *beam);
+int h2_beam_holds_proxies(h2_bucket_beam *beam);
 
 /**
  * Abort the beam. Will cleanup any buffered buckets and answer all send
  * and receives with APR_ECONNABORTED.
  * 
- * Call from the red side only.
+ * Call from the sender side only.
  */
 void h2_beam_abort(h2_bucket_beam *beam);
 
 /**
  * Close the beam. Sending an EOS bucket serves the same purpose. 
  * 
- * Call from the red side only.
+ * Call from the sender side only.
  */
 apr_status_t h2_beam_close(h2_bucket_beam *beam);
 
 /**
+ * Receives leaves the beam, e.g. will no longer read. This will
+ * interrupt any sender blocked writing and fail future send. 
+ * 
+ * Call from the receiver side only.
+ */
+apr_status_t h2_beam_leave(h2_bucket_beam *beam);
+
+int h2_beam_is_closed(h2_bucket_beam *beam);
+
+/**
  * Return APR_SUCCESS when all buckets in transit have been handled. 
  * When called with APR_BLOCK_READ and a mutex set, will wait until the green
  * side has consumed all data. Otherwise APR_EAGAIN is returned.
@@ -289,15 +307,9 @@ apr_status_t h2_beam_close(h2_bucket_bea
  * If a timeout is set on the beam, waiting might also time out and
  * return APR_ETIMEUP.
  *
- * Call from the red side only.
+ * Call from the sender side only.
  */
-apr_status_t h2_beam_shutdown(h2_bucket_beam *beam, apr_read_type_e block,
-                              int clear_buffers);
-
-void h2_beam_mutex_set(h2_bucket_beam *beam, 
-                       h2_beam_mutex_enter m_enter,
-                       struct apr_thread_cond_t *cond,
-                       void *m_ctx);
+apr_status_t h2_beam_wait_empty(h2_bucket_beam *beam, apr_read_type_e block);
 
 /** 
  * Set/get the timeout for blocking read/write operations. Only works
@@ -315,31 +327,53 @@ void h2_beam_buffer_size_set(h2_bucket_b
 apr_size_t h2_beam_buffer_size_get(h2_bucket_beam *beam);
 
 /**
- * Register a callback to be invoked on the red side with the
- * amount of bytes that have been consumed by the red side, since the
+ * Register a callback to be invoked on the sender side with the
+ * amount of bytes that have been consumed by the receiver, since the
  * last callback invocation or reset.
  * @param beam the beam to set the callback on
- * @param cb   the callback or NULL
+ * @param ev_cb the callback or NULL, called when bytes are consumed
+ * @param io_cb the callback or NULL, called on sender with bytes consumed
  * @param ctx  the context to use in callback invocation
  * 
- * Call from the red side, callbacks invoked on red side.
+ * Call from the sender side, io callbacks invoked on sender side, ev callback
+ * from any side.
  */
 void h2_beam_on_consumed(h2_bucket_beam *beam, 
-                         h2_beam_io_callback *cb, void *ctx);
+                         h2_beam_ev_callback *ev_cb,
+                         h2_beam_io_callback *io_cb, void *ctx);
+
+/**
+ * Call any registered consumed handler, if any changes have happened
+ * since the last invocation. 
+ * @return !=0 iff a handler has been called
+ *
+ * Needs to be invoked from the sending side.
+ */
+int h2_beam_report_consumption(h2_bucket_beam *beam);
 
 /**
- * Register a callback to be invoked on the red side with the
- * amount of bytes that have been consumed by the red side, since the
+ * Register a callback to be invoked on the receiver side with the
+ * amount of bytes that have been produces by the sender, since the
  * last callback invocation or reset.
  * @param beam the beam to set the callback on
- * @param cb   the callback or NULL
+ * @param io_cb the callback or NULL, called on receiver with bytes produced
  * @param ctx  the context to use in callback invocation
  * 
- * Call from the red side, callbacks invoked on red side.
+ * Call from the receiver side, callbacks invoked on either side.
  */
 void h2_beam_on_produced(h2_bucket_beam *beam, 
-                         h2_beam_io_callback *cb, void *ctx);
+                         h2_beam_io_callback *io_cb, void *ctx);
 
+/**
+ * Register a callback that may prevent a file from being beam as
+ * file handle, forcing the file content to be copied. Then no callback
+ * is set (NULL), file handles are transferred directly.
+ * @param beam the beam to set the callback on
+ * @param io_cb the callback or NULL, called on receiver with bytes produced
+ * @param ctx  the context to use in callback invocation
+ * 
+ * Call from the receiver side, callbacks invoked on either side.
+ */
 void h2_beam_on_file_beam(h2_bucket_beam *beam, 
                           h2_beam_can_beam_callback *cb, void *ctx);
 
@@ -360,4 +394,12 @@ int h2_beam_was_received(h2_bucket_beam
 
 apr_size_t h2_beam_get_files_beamed(h2_bucket_beam *beam);
 
+typedef apr_bucket *h2_bucket_beamer(h2_bucket_beam *beam, 
+                                     apr_bucket_brigade *dest,
+                                     const apr_bucket *src);
+
+void h2_register_bucket_beamer(h2_bucket_beamer *beamer);
+
+void h2_beam_log(h2_bucket_beam *beam, conn_rec *c, int level, const char *msg);
+
 #endif /* h2_bucket_beam_h */
diff -up --new-file httpd-2.4.23/modules/http2/h2_bucket_eoc.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_bucket_eoc.c
--- httpd-2.4.23/modules/http2/h2_bucket_eoc.c	2016-05-23 12:55:29.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_bucket_eoc.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,110 +0,0 @@
-/* Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <assert.h>
-#include <stddef.h>
-
-#include <httpd.h>
-#include <http_core.h>
-#include <http_connection.h>
-#include <http_log.h>
-
-#include "h2_private.h"
-#include "h2.h"
-#include "h2_mplx.h"
-#include "h2_session.h"
-#include "h2_bucket_eoc.h"
-
-typedef struct {
-    apr_bucket_refcount refcount;
-    h2_session *session;
-} h2_bucket_eoc;
-
-static apr_status_t bucket_cleanup(void *data)
-{
-    h2_session **psession = data;
-
-    if (*psession) {
-        /*
-         * If bucket_destroy is called after us, this prevents
-         * bucket_destroy from trying to destroy the pool again.
-         */
-        *psession = NULL;
-    }
-    return APR_SUCCESS;
-}
-
-static apr_status_t bucket_read(apr_bucket *b, const char **str,
-                                apr_size_t *len, apr_read_type_e block)
-{
-    (void)b;
-    (void)block;
-    *str = NULL;
-    *len = 0;
-    return APR_SUCCESS;
-}
-
-apr_bucket * h2_bucket_eoc_make(apr_bucket *b, h2_session *session)
-{
-    h2_bucket_eoc *h;
-
-    h = apr_bucket_alloc(sizeof(*h), b->list);
-    h->session = session;
-
-    b = apr_bucket_shared_make(b, h, 0, 0);
-    b->type = &h2_bucket_type_eoc;
-    
-    return b;
-}
-
-apr_bucket * h2_bucket_eoc_create(apr_bucket_alloc_t *list, h2_session *session)
-{
-    apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
-
-    APR_BUCKET_INIT(b);
-    b->free = apr_bucket_free;
-    b->list = list;
-    b = h2_bucket_eoc_make(b, session);
-    if (session) {
-        h2_bucket_eoc *h = b->data;
-        apr_pool_pre_cleanup_register(session->pool, &h->session, bucket_cleanup);
-    }
-    return b;
-}
-
-static void bucket_destroy(void *data)
-{
-    h2_bucket_eoc *h = data;
-
-    if (apr_bucket_shared_destroy(h)) {
-        h2_session *session = h->session;
-        apr_bucket_free(h);
-        if (session) {
-            h2_session_eoc_callback(session);
-            /* all is gone now */
-        }
-    }
-}
-
-const apr_bucket_type_t h2_bucket_type_eoc = {
-    "H2EOC", 5, APR_BUCKET_METADATA,
-    bucket_destroy,
-    bucket_read,
-    apr_bucket_setaside_noop,
-    apr_bucket_split_notimpl,
-    apr_bucket_shared_copy
-};
-
diff -up --new-file httpd-2.4.23/modules/http2/h2_bucket_eoc.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_bucket_eoc.h
--- httpd-2.4.23/modules/http2/h2_bucket_eoc.h	2016-05-23 12:55:29.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_bucket_eoc.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,32 +0,0 @@
-/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef mod_http2_h2_bucket_eoc_h
-#define mod_http2_h2_bucket_eoc_h
-
-struct h2_session;
-
-/** End Of HTTP/2 SESSION (H2EOC) bucket */
-extern const apr_bucket_type_t h2_bucket_type_eoc;
-
-#define H2_BUCKET_IS_H2EOC(e)     (e->type == &h2_bucket_type_eoc)
-
-apr_bucket * h2_bucket_eoc_make(apr_bucket *b, 
-                                struct h2_session *session);
-
-apr_bucket * h2_bucket_eoc_create(apr_bucket_alloc_t *list,
-                                  struct h2_session *session);
-
-#endif /* mod_http2_h2_bucket_eoc_h */
diff -up --new-file httpd-2.4.23/modules/http2/h2_bucket_eos.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_bucket_eos.c
--- httpd-2.4.23/modules/http2/h2_bucket_eos.c	2016-06-07 13:29:51.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_bucket_eos.c	2017-02-14 16:53:50.000000000 +0100
@@ -95,7 +95,7 @@ static void bucket_destroy(void *data)
         }
         apr_bucket_free(h);
         if (stream) {
-            h2_stream_eos_destroy(stream);
+            h2_stream_dispatch(stream, H2_SEV_EOS_SENT);
         }
     }
 }
diff -up --new-file httpd-2.4.23/modules/http2/h2_config.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_config.c
--- httpd-2.4.23/modules/http2/h2_config.c	2016-03-02 12:21:28.000000000 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_config.c	2017-03-31 21:41:01.000000000 +0200
@@ -48,12 +48,11 @@ static h2_config defconf = {
     -1,                     /* min workers */
     -1,                     /* max workers */
     10 * 60,                /* max workers idle secs */
-    64 * 1024,              /* stream max mem size */
+    32 * 1024,              /* stream max mem size */
     NULL,                   /* no alt-svcs */
     -1,                     /* alt-svc max age */
     0,                      /* serialize headers */
     -1,                     /* h2 direct mode */
-    -1,                     /* # session extra files */
     1,                      /* modern TLS only */
     -1,                     /* HTTP/1 Upgrade support */
     1024*1024,              /* TLS warmup size */
@@ -61,7 +60,9 @@ static h2_config defconf = {
     1,                      /* HTTP/2 server push enabled */
     NULL,                   /* map of content-type to priorities */
     256,                    /* push diary size */
-    
+    0,                      /* copy files across threads */
+    NULL,                   /* push list */
+    0,                      /* early hints, http status 103 */
 };
 
 void h2_config_init(apr_pool_t *pool)
@@ -73,7 +74,6 @@ static void *h2_config_create(apr_pool_t
                               const char *prefix, const char *x)
 {
     h2_config *conf = (h2_config *)apr_pcalloc(pool, sizeof(h2_config));
-    
     const char *s = x? x : "unknown";
     char *name = apr_pstrcat(pool, prefix, "[", s, "]", NULL);
     
@@ -87,7 +87,6 @@ static void *h2_config_create(apr_pool_t
     conf->alt_svc_max_age      = DEF_VAL;
     conf->serialize_headers    = DEF_VAL;
     conf->h2_direct            = DEF_VAL;
-    conf->session_extra_files  = DEF_VAL;
     conf->modern_tls_only      = DEF_VAL;
     conf->h2_upgrade           = DEF_VAL;
     conf->tls_warmup_size      = DEF_VAL;
@@ -95,7 +94,9 @@ static void *h2_config_create(apr_pool_t
     conf->h2_push              = DEF_VAL;
     conf->priorities           = NULL;
     conf->push_diary_size      = DEF_VAL;
-    
+    conf->copy_files           = DEF_VAL;
+    conf->push_list            = NULL;
+    conf->early_hints          = DEF_VAL;
     return conf;
 }
 
@@ -109,12 +110,11 @@ void *h2_config_create_dir(apr_pool_t *p
     return h2_config_create(pool, "dir", x);
 }
 
-void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
+static void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv)
 {
     h2_config *base = (h2_config *)basev;
     h2_config *add = (h2_config *)addv;
     h2_config *n = (h2_config *)apr_pcalloc(pool, sizeof(h2_config));
-
     char *name = apr_pstrcat(pool, "merged[", add->name, ", ", base->name, "]", NULL);
     n->name = name;
 
@@ -128,7 +128,6 @@ void *h2_config_merge(apr_pool_t *pool,
     n->alt_svc_max_age      = H2_CONFIG_GET(add, base, alt_svc_max_age);
     n->serialize_headers    = H2_CONFIG_GET(add, base, serialize_headers);
     n->h2_direct            = H2_CONFIG_GET(add, base, h2_direct);
-    n->session_extra_files  = H2_CONFIG_GET(add, base, session_extra_files);
     n->modern_tls_only      = H2_CONFIG_GET(add, base, modern_tls_only);
     n->h2_upgrade           = H2_CONFIG_GET(add, base, h2_upgrade);
     n->tls_warmup_size      = H2_CONFIG_GET(add, base, tls_warmup_size);
@@ -141,10 +140,27 @@ void *h2_config_merge(apr_pool_t *pool,
         n->priorities       = add->priorities? add->priorities : base->priorities;
     }
     n->push_diary_size      = H2_CONFIG_GET(add, base, push_diary_size);
-    
+    n->copy_files           = H2_CONFIG_GET(add, base, copy_files);
+    if (add->push_list && base->push_list) {
+        n->push_list        = apr_array_append(pool, base->push_list, add->push_list);
+    }
+    else {
+        n->push_list        = add->push_list? add->push_list : base->push_list;
+    }
+    n->early_hints          = H2_CONFIG_GET(add, base, early_hints);
     return n;
 }
 
+void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv)
+{
+    return h2_config_merge(pool, basev, addv);
+}
+
+void *h2_config_merge_svr(apr_pool_t *pool, void *basev, void *addv)
+{
+    return h2_config_merge(pool, basev, addv);
+}
+
 int h2_config_geti(const h2_config *conf, h2_config_var_t var)
 {
     return (int)h2_config_geti64(conf, var);
@@ -175,8 +191,6 @@ apr_int64_t h2_config_geti64(const h2_co
             return H2_CONFIG_GET(conf, &defconf, h2_upgrade);
         case H2_CONF_DIRECT:
             return H2_CONFIG_GET(conf, &defconf, h2_direct);
-        case H2_CONF_SESSION_FILES:
-            return H2_CONFIG_GET(conf, &defconf, session_extra_files);
         case H2_CONF_TLS_WARMUP_SIZE:
             return H2_CONFIG_GET(conf, &defconf, tls_warmup_size);
         case H2_CONF_TLS_COOLDOWN_SECS:
@@ -185,6 +199,10 @@ apr_int64_t h2_config_geti64(const h2_co
             return H2_CONFIG_GET(conf, &defconf, h2_push);
         case H2_CONF_PUSH_DIARY_SIZE:
             return H2_CONFIG_GET(conf, &defconf, push_diary_size);
+        case H2_CONF_COPY_FILES:
+            return H2_CONFIG_GET(conf, &defconf, copy_files);
+        case H2_CONF_EARLY_HINTS:
+            return H2_CONFIG_GET(conf, &defconf, early_hints);
         default:
             return DEF_VAL;
     }
@@ -194,7 +212,7 @@ const h2_config *h2_config_sget(server_r
 {
     h2_config *cfg = (h2_config *)ap_get_module_config(s->module_config, 
                                                        &http2_module);
-    AP_DEBUG_ASSERT(cfg);
+    ap_assert(cfg);
     return cfg;
 }
 
@@ -286,7 +304,7 @@ static const char *h2_conf_set_stream_ma
 static const char *h2_add_alt_svc(cmd_parms *parms,
                                   void *arg, const char *value)
 {
-    if (value && strlen(value)) {
+    if (value && *value) {
         h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
         h2_alt_svc *as = h2_alt_svc_parse(value, parms->pool);
         if (!as) {
@@ -313,13 +331,11 @@ static const char *h2_conf_set_alt_svc_m
 static const char *h2_conf_set_session_extra_files(cmd_parms *parms,
                                                    void *arg, const char *value)
 {
-    h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
-    apr_int64_t max = (int)apr_atoi64(value);
-    if (max < 0) {
-        return "value must be a non-negative number";
-    }
-    cfg->session_extra_files = (int)max;
+    /* deprecated, ignore */
     (void)arg;
+    (void)value;
+    ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, parms->pool, /* NO LOGNO */
+                  "H2SessionExtraFiles is obsolete and will be ignored");
     return NULL;
 }
 
@@ -384,7 +400,7 @@ static const char *h2_conf_add_push_prio
     h2_priority *priority;
     int weight;
     
-    if (!strlen(ctype)) {
+    if (!*ctype) {
         return "1st argument must be a mime-type, like 'text/css' or '*'";
     }
     
@@ -500,6 +516,93 @@ static const char *h2_conf_set_push_diar
     return NULL;
 }
 
+static const char *h2_conf_set_copy_files(cmd_parms *parms,
+                                          void *arg, const char *value)
+{
+    h2_config *cfg = (h2_config *)arg;
+    if (!strcasecmp(value, "On")) {
+        cfg->copy_files = 1;
+        return NULL;
+    }
+    else if (!strcasecmp(value, "Off")) {
+        cfg->copy_files = 0;
+        return NULL;
+    }
+    
+    (void)arg;
+    return "value must be On or Off";
+}
+
+static void add_push(apr_pool_t *pool, h2_config *conf, h2_push_res *push)
+{
+    h2_push_res *new;
+    if (!conf->push_list) {
+        conf->push_list = apr_array_make(pool, 10, sizeof(*push));
+    }
+    new = apr_array_push(conf->push_list);
+    new->uri_ref = push->uri_ref;
+    new->critical = push->critical;
+}
+
+static const char *h2_conf_add_push_res(cmd_parms *cmd, void *dirconf,
+                                        const char *arg1, const char *arg2,
+                                        const char *arg3)
+{
+    h2_config *dconf = (h2_config*)dirconf ;
+    h2_config *sconf = (h2_config*)h2_config_sget(cmd->server);
+    h2_push_res push;
+    const char *last = arg3;
+    
+    memset(&push, 0, sizeof(push));
+    if (!strcasecmp("add", arg1)) {
+        push.uri_ref = arg2;
+    }
+    else {
+        push.uri_ref = arg1;
+        last = arg2;
+        if (arg3) {
+            return "too many parameter";
+        }
+    }
+    
+    if (last) {
+        if (!strcasecmp("critical", last)) {
+            push.critical = 1;
+        }
+        else {
+            return "unknown last parameter";
+        }
+    }
+
+    /* server command? set both */
+    if (cmd->path == NULL) {
+        add_push(cmd->pool, sconf, &push);
+        add_push(cmd->pool, dconf, &push);
+    }
+    else {
+        add_push(cmd->pool, dconf, &push);
+    }
+
+    return NULL;
+}
+
+static const char *h2_conf_set_early_hints(cmd_parms *parms,
+                                           void *arg, const char *value)
+{
+    h2_config *cfg = (h2_config *)h2_config_sget(parms->server);
+    if (!strcasecmp(value, "On")) {
+        cfg->early_hints = 1;
+        return NULL;
+    }
+    else if (!strcasecmp(value, "Off")) {
+        cfg->early_hints = 0;
+        return NULL;
+    }
+    
+    (void)arg;
+    return "value must be On or Off";
+}
+
 #define AP_END_CMD     AP_INIT_TAKE1(NULL, NULL, NULL, RSRC_CONF, NULL)
 
 const command_rec h2_cmds[] = {
@@ -528,7 +631,7 @@ const command_rec h2_cmds[] = {
     AP_INIT_TAKE1("H2Direct", h2_conf_set_direct, NULL,
                   RSRC_CONF, "on to enable direct HTTP/2 mode"),
     AP_INIT_TAKE1("H2SessionExtraFiles", h2_conf_set_session_extra_files, NULL,
-                  RSRC_CONF, "number of extra file a session might keep open"),
+                  RSRC_CONF, "number of extra file a session might keep open (obsolete)"),
     AP_INIT_TAKE1("H2TLSWarmUpSize", h2_conf_set_tls_warmup_size, NULL,
                   RSRC_CONF, "number of bytes on TLS connection before doing max writes"),
     AP_INIT_TAKE1("H2TLSCoolDownSecs", h2_conf_set_tls_cooldown_secs, NULL,
@@ -539,6 +642,12 @@ const command_rec h2_cmds[] = {
                   RSRC_CONF, "define priority of PUSHed resources per content type"),
     AP_INIT_TAKE1("H2PushDiarySize", h2_conf_set_push_diary_size, NULL,
                   RSRC_CONF, "size of push diary"),
+    AP_INIT_TAKE1("H2CopyFiles", h2_conf_set_copy_files, NULL,
+                  OR_FILEINFO, "on to perform copy of file data"),
+    AP_INIT_TAKE123("H2PushResource", h2_conf_add_push_res, NULL,
+                   OR_FILEINFO, "add a resource to be pushed in this location/on this server."),
+    AP_INIT_TAKE1("H2EarlyHints", h2_conf_set_early_hints, NULL,
+                  RSRC_CONF, "on to enable interim status 103 responses"),
     AP_END_CMD
 };
 
diff -up --new-file httpd-2.4.23/modules/http2/h2_config.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_config.h
--- httpd-2.4.23/modules/http2/h2_config.h	2016-03-02 12:21:28.000000000 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_config.h	2017-03-31 21:41:01.000000000 +0200
@@ -33,17 +33,24 @@ typedef enum {
     H2_CONF_ALT_SVC_MAX_AGE,
     H2_CONF_SER_HEADERS,
     H2_CONF_DIRECT,
-    H2_CONF_SESSION_FILES,
     H2_CONF_MODERN_TLS_ONLY,
     H2_CONF_UPGRADE,
     H2_CONF_TLS_WARMUP_SIZE,
     H2_CONF_TLS_COOLDOWN_SECS,
     H2_CONF_PUSH,
     H2_CONF_PUSH_DIARY_SIZE,
+    H2_CONF_COPY_FILES,
+    H2_CONF_EARLY_HINTS,
 } h2_config_var_t;
 
 struct apr_hash_t;
 struct h2_priority;
+struct h2_push_res;
+
+typedef struct h2_push_res {
+    const char *uri_ref;
+    int critical;
+} h2_push_res;
 
 /* Apache httpd module configuration for h2. */
 typedef struct h2_config {
@@ -59,7 +66,6 @@ typedef struct h2_config {
     int serialize_headers;        /* Use serialized HTTP/1.1 headers for 
                                      processing, better compatibility */
     int h2_direct;                /* if mod_h2 is active directly */
-    int session_extra_files;      /* # of extra files a session may keep open */  
     int modern_tls_only;          /* Accept only modern TLS in HTTP/2 connections */  
     int h2_upgrade;               /* Allow HTTP/1 upgrade to h2/h2c */
     apr_int64_t tls_warmup_size;  /* Amount of TLS data to send before going full write size */
@@ -68,14 +74,16 @@ typedef struct h2_config {
     struct apr_hash_t *priorities;/* map of content-type to h2_priority records */
     
     int push_diary_size;          /* # of entries in push diary */
+    int copy_files;               /* if files shall be copied vs setaside on output */
+    apr_array_header_t *push_list;/* list of h2_push_res configurations */
+    int early_hints;              /* support status code 103 */
 } h2_config;
 
 
 void *h2_config_create_dir(apr_pool_t *pool, char *x);
+void *h2_config_merge_dir(apr_pool_t *pool, void *basev, void *addv);
 void *h2_config_create_svr(apr_pool_t *pool, server_rec *s);
-void *h2_config_merge(apr_pool_t *pool, void *basev, void *addv);
-
-apr_status_t h2_config_apply_header(const h2_config *config, request_rec *r);
+void *h2_config_merge_svr(apr_pool_t *pool, void *basev, void *addv);
 
 extern const command_rec h2_cmds[];
 
diff -up --new-file httpd-2.4.23/modules/http2/h2_conn.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_conn.c
--- httpd-2.4.23/modules/http2/h2_conn.c	2016-04-28 14:43:02.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_conn.c	2017-07-04 14:34:15.000000000 +0200
@@ -14,6 +14,7 @@
  */
 
 #include <assert.h>
+#include <apr_strings.h>
 
 #include <ap_mpm.h>
 
@@ -25,6 +26,8 @@
 #include <http_protocol.h>
 #include <http_request.h>
 
+#include <mpm_common.h>
+
 #include "h2_private.h"
 #include "h2.h"
 #include "h2_config.h"
@@ -35,7 +38,6 @@
 #include "h2_stream.h"
 #include "h2_h2.h"
 #include "h2_task.h"
-#include "h2_worker.h"
 #include "h2_workers.h"
 #include "h2_conn.h"
 #include "h2_version.h"
@@ -45,6 +47,7 @@ static struct h2_workers *workers;
 static h2_mpm_type_t mpm_type = H2_MPM_UNKNOWN;
 static module *mpm_module;
 static int async_mpm;
+static int mpm_supported = 1;
 static apr_socket_t *dummy_socket;
 
 static void check_modules(int force) 
@@ -74,11 +77,18 @@ static void check_modules(int force)
             else if (!strcmp("prefork.c", m->name)) {
                 mpm_type = H2_MPM_PREFORK;
                 mpm_module = m;
+                /* While http2 can work really well on prefork, it collides
+                 * today's use case for prefork: runnning single-thread app engines
+                 * like php. If we restrict h2_workers to 1 per process, php will
+                 * work fine, but browser will be limited to 1 active request at a
+                 * time. */
+                mpm_supported = 0;
                 break;
             }
             else if (!strcmp("simple_api.c", m->name)) {
                 mpm_type = H2_MPM_SIMPLE;
                 mpm_module = m;
+                mpm_supported = 0;
                 break;
             }
             else if (!strcmp("mpm_winnt.c", m->name)) {
@@ -100,12 +110,11 @@ apr_status_t h2_conn_child_init(apr_pool
 {
     const h2_config *config = h2_config_sget(s);
     apr_status_t status = APR_SUCCESS;
-    int minw, maxw, max_tx_handles, n;
+    int minw, maxw;
     int max_threads_per_child = 0;
     int idle_secs = 0;
 
     check_modules(1);
-    
     ap_mpm_query(AP_MPMQ_MAX_THREADS, &max_threads_per_child);
     
     status = ap_mpm_query(AP_MPMQ_IS_ASYNC, &async_mpm);
@@ -123,34 +132,18 @@ apr_status_t h2_conn_child_init(apr_pool
         minw = max_threads_per_child;
     }
     if (maxw <= 0) {
-        maxw = minw;
+        /* As a default, this seems to work quite well under mpm_event. 
+         * For people enabling http2 under mpm_prefork, start 4 threads unless 
+         * configured otherwise. People get unhappy if their http2 requests are 
+         * blocking each other. */
+        maxw = H2MAX(3 * minw / 2, 4);
     }
     
-    /* How many file handles is it safe to use for transfer
-     * to the master connection to be streamed out? 
-     * Is there a portable APR rlimit on NOFILES? Have not
-     * found it. And if, how many of those would we set aside?
-     * This leads all into a process wide handle allocation strategy
-     * which ultimately would limit the number of accepted connections
-     * with the assumption of implicitly reserving n handles for every 
-     * connection and requiring modules with excessive needs to allocate
-     * from a central pool.
-     */
-    n = h2_config_geti(config, H2_CONF_SESSION_FILES);
-    if (n < 0) {
-        max_tx_handles = maxw * 2;
-    }
-    else {
-        max_tx_handles = maxw * n;
-    }
-    
-    ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
-                 "h2_workers: min=%d max=%d, mthrpchild=%d, tx_files=%d", 
-                 minw, maxw, max_threads_per_child, max_tx_handles);
-    workers = h2_workers_create(s, pool, minw, maxw, max_tx_handles);
-    
     idle_secs = h2_config_geti(config, H2_CONF_MAX_WORKER_IDLE_SECS);
-    h2_workers_set_max_idle_secs(workers, idle_secs);
+    ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
+                 "h2_workers: min=%d max=%d, mthrpchild=%d, idle_secs=%d", 
+                 minw, maxw, max_threads_per_child, idle_secs);
+    workers = h2_workers_create(s, pool, minw, maxw, idle_secs);
  
     ap_register_input_filter("H2_IN", h2_filter_core_input,
                              NULL, AP_FTYPE_CONNECTION);
@@ -171,6 +164,18 @@ h2_mpm_type_t h2_conn_mpm_type(void)
     return mpm_type;
 }
 
+const char *h2_conn_mpm_name(void)
+{
+    check_modules(0);
+    return mpm_module? mpm_module->name : "unknown";
+}
+
+int h2_mpm_supported(void)
+{
+    check_modules(0);
+    return mpm_supported;
+}
+
 static module *h2_conn_mpm_module(void)
 {
     check_modules(0);
@@ -180,6 +185,7 @@ static module *h2_conn_mpm_module(void)
 apr_status_t h2_conn_setup(h2_ctx *ctx, conn_rec *c, request_rec *r)
 {
     h2_session *session;
+    apr_status_t status;
     
     if (!workers) {
         ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, c, APLOGNO(02911) 
@@ -188,31 +194,37 @@ apr_status_t h2_conn_setup(h2_ctx *ctx,
     }
     
     if (r) {
-        session = h2_session_rcreate(r, ctx, workers);
+        status = h2_session_rcreate(&session, r, ctx, workers);
     }
     else {
-        session = h2_session_create(c, ctx, workers);
+        status = h2_session_create(&session, c, ctx, workers);
     }
 
-    h2_ctx_session_set(ctx, session);
-    
-    return APR_SUCCESS;
+    if (status == APR_SUCCESS) {
+        h2_ctx_session_set(ctx, session);
+    }
+    return status;
 }
 
 apr_status_t h2_conn_run(struct h2_ctx *ctx, conn_rec *c)
 {
     apr_status_t status;
     int mpm_state = 0;
+    h2_session *session = h2_ctx_session_get(ctx);
     
+    ap_assert(session);
     do {
         if (c->cs) {
             c->cs->sense = CONN_SENSE_DEFAULT;
+            c->cs->state = CONN_STATE_HANDLER;
         }
-        status = h2_session_process(h2_ctx_session_get(ctx), async_mpm);
+    
+        status = h2_session_process(session, async_mpm);
         
         if (APR_STATUS_IS_EOF(status)) {
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03045)
-                          "h2_session(%ld): process, closing conn", c->id);
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, 
+                          H2_SSSN_LOG(APLOGNO(03045), session, 
+                          "process, closing conn"));
             c->keepalive = AP_CONN_CLOSE;
         }
         else {
@@ -226,53 +238,78 @@ apr_status_t h2_conn_run(struct h2_ctx *
              && c->keepalive == AP_CONN_KEEPALIVE 
              && mpm_state != AP_MPMQ_STOPPING);
     
+    if (c->cs) {
+        switch (session->state) {
+            case H2_SESSION_ST_INIT:
+            case H2_SESSION_ST_CLEANUP:
+            case H2_SESSION_ST_DONE:
+            case H2_SESSION_ST_IDLE:
+                c->cs->state = CONN_STATE_WRITE_COMPLETION;
+                break;
+            case H2_SESSION_ST_BUSY:
+            case H2_SESSION_ST_WAIT:
+            default:
+                c->cs->state = CONN_STATE_HANDLER;
+                break;
+                
+        }
+    }
+    
     return DONE;
 }
 
 apr_status_t h2_conn_pre_close(struct h2_ctx *ctx, conn_rec *c)
 {
-    apr_status_t status;
-    
-    status = h2_session_pre_close(h2_ctx_session_get(ctx), async_mpm);
-    if (status == APR_SUCCESS) {
-        return DONE; /* This is the same, right? */
+    h2_session *session = h2_ctx_session_get(ctx);
+    if (session) {
+        apr_status_t status = h2_session_pre_close(session, async_mpm);
+        return (status == APR_SUCCESS)? DONE : status;
     }
-    return status;
+    return DONE;
 }
 
-conn_rec *h2_slave_create(conn_rec *master, apr_pool_t *parent,
-                          apr_allocator_t *allocator)
+conn_rec *h2_slave_create(conn_rec *master, int slave_id, apr_pool_t *parent)
 {
+    apr_allocator_t *allocator;
+    apr_status_t status;
     apr_pool_t *pool;
     conn_rec *c;
     void *cfg;
+    module *mpm;
     
-    AP_DEBUG_ASSERT(master);
+    ap_assert(master);
     ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, master,
-                  "h2_conn(%ld): create slave", master->id);
+                  "h2_stream(%ld-%d): create slave", master->id, slave_id);
     
     /* We create a pool with its own allocator to be used for
      * processing a request. This is the only way to have the processing
      * independant of its parent pool in the sense that it can work in
-     * another thread.
+     * another thread. Also, the new allocator needs its own mutex to
+     * synchronize sub-pools.
      */
-    if (!allocator) {
-        apr_allocator_create(&allocator);
+    apr_allocator_create(&allocator);
+    apr_allocator_max_free_set(allocator, ap_max_mem_free);
+    status = apr_pool_create_ex(&pool, parent, NULL, allocator);
+    if (status != APR_SUCCESS) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, master, 
+                      APLOGNO(10004) "h2_session(%ld-%d): create slave pool",
+                      master->id, slave_id);
+        return NULL;
     }
-    apr_pool_create_ex(&pool, parent, NULL, allocator);
-    apr_pool_tag(pool, "h2_slave_conn");
     apr_allocator_owner_set(allocator, pool);
-
+    apr_pool_tag(pool, "h2_slave_conn");
+ 
     c = (conn_rec *) apr_palloc(pool, sizeof(conn_rec));
     if (c == NULL) {
         ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, master, 
-                      APLOGNO(02913) "h2_task: creating conn");
+                      APLOGNO(02913) "h2_session(%ld-%d): create slave",
+                      master->id, slave_id);
+        apr_pool_destroy(pool);
         return NULL;
     }
     
     memcpy(c, master, sizeof(conn_rec));
-           
-    /* Replace these */
+        
     c->master                 = master;
     c->pool                   = pool;   
     c->conn_config            = ap_create_conn_config(pool);
@@ -282,11 +319,15 @@ conn_rec *h2_slave_create(conn_rec *mast
     c->bucket_alloc           = apr_bucket_alloc_create(pool);
     c->data_in_input_filters  = 0;
     c->data_in_output_filters = 0;
+    /* prevent mpm_event from making wrong assumptions about this connection,
+     * like e.g. using its socket for an async read check. */
     c->clogging_input_filters = 1;
     c->log                    = NULL;
-    c->log_id                 = NULL;
+    c->log_id                 = apr_psprintf(pool, "%ld-%d", 
+                                             master->id, slave_id);
     /* Simulate that we had already a request on this connection. */
     c->keepalives             = 1;
+    c->aborted                = 0;
     /* We cannot install the master connection socket on the slaves, as
      * modules mess with timeouts/blocking of the socket, with
      * unwanted side effects to the master connection processing.
@@ -299,29 +340,22 @@ conn_rec *h2_slave_create(conn_rec *mast
     /* TODO: not all mpm modules have learned about slave connections yet.
      * copy their config from master to slave.
      */
-    if (h2_conn_mpm_module()) {
-        cfg = ap_get_module_config(master->conn_config, h2_conn_mpm_module());
-        ap_set_module_config(c->conn_config, h2_conn_mpm_module(), cfg);
+    if ((mpm = h2_conn_mpm_module()) != NULL) {
+        cfg = ap_get_module_config(master->conn_config, mpm);
+        ap_set_module_config(c->conn_config, mpm, cfg);
     }
 
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c, 
+                  "h2_stream(%ld-%d): created slave", master->id, slave_id);
     return c;
 }
 
-void h2_slave_destroy(conn_rec *slave, apr_allocator_t **pallocator)
+void h2_slave_destroy(conn_rec *slave)
 {
-    apr_pool_t *parent;
-    apr_allocator_t *allocator = apr_pool_allocator_get(slave->pool);
     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, slave,
-                  "h2_slave_conn(%ld): destroy (task=%s)", slave->id,
+                  "h2_stream(%s): destroy slave", 
                   apr_table_get(slave->notes, H2_TASK_ID_NOTE));
-    /* Attache the allocator to the parent pool and return it for
-     * reuse, otherwise the own is still the slave pool and it will
-     * get destroyed with it. */
-    parent = apr_pool_parent_get(slave->pool);
-    if (pallocator && parent) {
-        apr_allocator_owner_set(allocator, parent);
-        *pallocator = allocator;
-    }
+    slave->sbh = NULL;
     apr_pool_destroy(slave->pool);
 }
 
diff -up --new-file httpd-2.4.23/modules/http2/h2_conn.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_conn.h
--- httpd-2.4.23/modules/http2/h2_conn.h	2016-03-17 17:54:05.000000000 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_conn.h	2017-07-04 14:34:15.000000000 +0200
@@ -31,7 +31,7 @@ apr_status_t h2_conn_setup(struct h2_ctx
 /**
  * Run the HTTP/2 connection in synchronous fashion. 
  * Return when the HTTP/2 session is done
- * and the connection will close or a fatal error occured.
+ * and the connection will close or a fatal error occurred.
  *
  * @param ctx the http2 context to run
  * @return APR_SUCCESS when session is done.
@@ -64,11 +64,11 @@ typedef enum {
 
 /* Returns the type of MPM module detected */
 h2_mpm_type_t h2_conn_mpm_type(void);
+const char *h2_conn_mpm_name(void);
+int h2_mpm_supported(void);
 
-
-conn_rec *h2_slave_create(conn_rec *master, apr_pool_t *parent,
-                          apr_allocator_t *allocator);
-void h2_slave_destroy(conn_rec *slave, apr_allocator_t **pallocator);
+conn_rec *h2_slave_create(conn_rec *master, int slave_id, apr_pool_t *parent);
+void h2_slave_destroy(conn_rec *slave);
 
 apr_status_t h2_slave_run_pre_connection(conn_rec *slave, apr_socket_t *csd);
 void h2_slave_run_connection(conn_rec *slave);
diff -up --new-file httpd-2.4.23/modules/http2/h2_conn_io.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_conn_io.c
--- httpd-2.4.23/modules/http2/h2_conn_io.c	2016-06-22 15:30:24.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_conn_io.c	2017-02-22 11:07:46.000000000 +0100
@@ -24,7 +24,6 @@
 #include <http_request.h>
 
 #include "h2_private.h"
-#include "h2_bucket_eoc.h"
 #include "h2_bucket_eos.h"
 #include "h2_config.h"
 #include "h2_conn_io.h"
@@ -39,6 +38,7 @@
  *      - TLS overhead (60-100) 
  * ~= 1300 bytes */
 #define WRITE_SIZE_INITIAL    1300
+
 /* Calculated like this: max TLS record size 16*1024
  *   - 40 (IP) - 20 (TCP) - 40 (TCP options) 
  *    - TLS overhead (60-100) 
@@ -72,9 +72,6 @@ static void h2_conn_io_bb_log(conn_rec *
                 else if (AP_BUCKET_IS_EOR(b)) {
                     off += apr_snprintf(buffer+off, bmax-off, "eor ");
                 }
-                else if (H2_BUCKET_IS_H2EOC(b)) {
-                    off += apr_snprintf(buffer+off, bmax-off, "h2eoc ");
-                }
                 else if (H2_BUCKET_IS_H2EOS(b)) {
                     off += apr_snprintf(buffer+off, bmax-off, "h2eos ");
                 }
@@ -120,20 +117,20 @@ static void h2_conn_io_bb_log(conn_rec *
         line = *buffer? buffer : "(empty)";
     }
     /* Intentional no APLOGNO */
-    ap_log_cerror(APLOG_MARK, level, 0, c, "bb_dump(%ld-%d)-%s: %s", 
-                  c->id, stream_id, tag, line);
+    ap_log_cerror(APLOG_MARK, level, 0, c, "h2_session(%ld)-%s: %s", 
+                  c->id, tag, line);
 
 }
 
 apr_status_t h2_conn_io_init(h2_conn_io *io, conn_rec *c, 
                              const h2_config *cfg)
 {
-    io->c             = c;
-    io->output        = apr_brigade_create(c->pool, c->bucket_alloc);
-    io->is_tls        = h2_h2_is_tls(c);
-    io->buffer_output = io->is_tls;
-    io->pass_threshold = h2_config_geti64(cfg, H2_CONF_STREAM_MAX_MEM) / 2;
-    
+    io->c              = c;
+    io->output         = apr_brigade_create(c->pool, c->bucket_alloc);
+    io->is_tls         = h2_h2_is_tls(c);
+    io->buffer_output  = io->is_tls;
+    io->flush_threshold = (apr_size_t)h2_config_geti64(cfg, H2_CONF_STREAM_MAX_MEM);
+
     if (io->is_tls) {
         /* This is what we start with, 
          * see https://issues.apache.org/jira/browse/TS-2503 
@@ -161,8 +158,6 @@ apr_status_t h2_conn_io_init(h2_conn_io
     return APR_SUCCESS;
 }
 
-#define LOG_SCRATCH 0
-
 static void append_scratch(h2_conn_io *io) 
 {
     if (io->scratch && io->slen > 0) {
@@ -170,11 +165,6 @@ static void append_scratch(h2_conn_io *i
                                                apr_bucket_free,
                                                io->c->bucket_alloc);
         APR_BRIGADE_INSERT_TAIL(io->output, b);
-#if LOG_SCRATCH
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, io->c, APLOGNO(03386)
-                      "h2_conn_io(%ld): append_scratch(%ld)", 
-                      io->c->id, (long)io->slen);
-#endif
         io->scratch = NULL;
         io->slen = io->ssize = 0;
     }
@@ -206,7 +196,7 @@ static apr_status_t read_to_scratch(h2_c
         return APR_SUCCESS;
     }
     
-    AP_DEBUG_ASSERT(b->length <= (io->ssize - io->slen));
+    ap_assert(b->length <= (io->ssize - io->slen));
     if (APR_BUCKET_IS_FILE(b)) {
         apr_bucket_file *f = (apr_bucket_file *)b->data;
         apr_file_t *fd = f->fd;
@@ -222,11 +212,6 @@ static apr_status_t read_to_scratch(h2_c
             return status;
         }
         status = apr_file_read(fd, io->scratch + io->slen, &len);
-#if LOG_SCRATCH
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, io->c, APLOGNO(03387)
-                      "h2_conn_io(%ld): FILE_to_scratch(%ld)", 
-                      io->c->id, (long)len); 
-#endif
         if (status != APR_SUCCESS && status != APR_EOF) {
             return status;
         }
@@ -235,11 +220,6 @@ static apr_status_t read_to_scratch(h2_c
     else {
         status = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
         if (status == APR_SUCCESS) {
-#if LOG_SCRATCH
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, io->c, APLOGNO(03388)
-                          "h2_conn_io(%ld): read_to_scratch(%ld)", 
-                          io->c->id, (long)b->length); 
-#endif
             memcpy(io->scratch+io->slen, data, len);
             io->slen += len;
         }
@@ -255,54 +235,46 @@ static void check_write_size(h2_conn_io
         /* long time not written, reset write size */
         io->write_size = WRITE_SIZE_INITIAL;
         io->bytes_written = 0;
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->c,
-                      "h2_conn_io(%ld): timeout write size reset to %ld", 
-                      (long)io->c->id, (long)io->write_size);
     }
     else if (io->write_size < WRITE_SIZE_MAX 
              && io->bytes_written >= io->warmup_size) {
         /* connection is hot, use max size */
         io->write_size = WRITE_SIZE_MAX;
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, io->c,
-                      "h2_conn_io(%ld): threshold reached, write size now %ld", 
-                      (long)io->c->id, (long)io->write_size);
     }
 }
 
-static apr_status_t pass_output(h2_conn_io *io, int flush, int eoc)
+static apr_status_t pass_output(h2_conn_io *io, int flush)
 {
     conn_rec *c = io->c;
+    apr_bucket_brigade *bb = io->output;
     apr_bucket *b;
     apr_off_t bblen;
     apr_status_t status;
     
     append_scratch(io);
-    if (flush) {
+    if (flush && !io->is_flushed) {
         b = apr_bucket_flush_create(c->bucket_alloc);
-        APR_BRIGADE_INSERT_TAIL(io->output, b);
+        APR_BRIGADE_INSERT_TAIL(bb, b);
     }
     
-    if (APR_BRIGADE_EMPTY(io->output)) {
+    if (APR_BRIGADE_EMPTY(bb)) {
         return APR_SUCCESS;
     }
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE4, 0, c, "h2_conn_io: pass_output");
     ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, NULL);
-    apr_brigade_length(io->output, 0, &bblen);
+    apr_brigade_length(bb, 0, &bblen);
+    h2_conn_io_bb_log(c, 0, APLOG_TRACE2, "out", bb);
     
-    h2_conn_io_bb_log(c, 0, APLOG_TRACE2, "master conn pass", io->output);
-    status = ap_pass_brigade(c->output_filters, io->output);
-
-    /* careful with access after this, as we might have flushed an EOC bucket
-     * that de-allocated us all. */
-    if (!eoc) {
-        apr_brigade_cleanup(io->output);
-        if (status == APR_SUCCESS) {
-            io->bytes_written += (apr_size_t)bblen;
-            io->last_write = apr_time_now();
+    status = ap_pass_brigade(c->output_filters, bb);
+    if (status == APR_SUCCESS) {
+        io->bytes_written += (apr_size_t)bblen;
+        io->last_write = apr_time_now();
+        if (flush) {
+            io->is_flushed = 1;
         }
     }
-    
+    apr_brigade_cleanup(bb);
+
     if (status != APR_SUCCESS) {
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03044)
                       "h2_conn_io(%ld): pass_out brigade %ld bytes",
@@ -311,16 +283,27 @@ static apr_status_t pass_output(h2_conn_
     return status;
 }
 
-apr_status_t h2_conn_io_flush(h2_conn_io *io)
+int h2_conn_io_needs_flush(h2_conn_io *io)
 {
-    return pass_output(io, 1, 0);
+    if (!io->is_flushed) {
+        apr_off_t len = h2_brigade_mem_size(io->output);
+        if (len > io->flush_threshold) {
+            return 1;
+        }
+        /* if we do not exceed flush length due to memory limits,
+         * we want at least flush when we have that amount of data. */
+        apr_brigade_length(io->output, 0, &len);
+        return len > (4 * io->flush_threshold);
+    }
+    return 0;
 }
 
-apr_status_t h2_conn_io_write_eoc(h2_conn_io *io, h2_session *session)
+apr_status_t h2_conn_io_flush(h2_conn_io *io)
 {
-    apr_bucket *b = h2_bucket_eoc_create(io->c->bucket_alloc, session);
-    APR_BRIGADE_INSERT_TAIL(io->output, b);
-    return pass_output(io, 1, 1);
+    apr_status_t status;
+    status = pass_output(io, 1);
+    check_write_size(io);
+    return status;
 }
 
 apr_status_t h2_conn_io_write(h2_conn_io *io, const char *data, size_t length)
@@ -328,25 +311,19 @@ apr_status_t h2_conn_io_write(h2_conn_io
     apr_status_t status = APR_SUCCESS;
     apr_size_t remain;
     
+    if (length > 0) {
+        io->is_flushed = 0;
+    }
+    
     if (io->buffer_output) {
         while (length > 0) {
             remain = assure_scratch_space(io);
             if (remain >= length) {
-#if LOG_SCRATCH
-                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, io->c, APLOGNO(03389)
-                              "h2_conn_io(%ld): write_to_scratch(%ld)", 
-                              io->c->id, (long)length); 
-#endif
                 memcpy(io->scratch + io->slen, data, length);
                 io->slen += length;
                 length = 0;
             }
             else {
-#if LOG_SCRATCH
-                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, io->c, APLOGNO(03390)
-                              "h2_conn_io(%ld): write_to_scratch(%ld)", 
-                              io->c->id, (long)remain); 
-#endif
                 memcpy(io->scratch + io->slen, data, remain);
                 io->slen += remain;
                 data += remain;
@@ -365,7 +342,10 @@ apr_status_t h2_conn_io_pass(h2_conn_io
     apr_bucket *b;
     apr_status_t status = APR_SUCCESS;
     
-    check_write_size(io);
+    if (!APR_BRIGADE_EMPTY(bb)) {
+        io->is_flushed = 0;
+    }
+
     while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
         b = APR_BRIGADE_FIRST(bb);
         
@@ -384,11 +364,6 @@ apr_status_t h2_conn_io_pass(h2_conn_io
                     /* complete write_size bucket, append unchanged */
                     APR_BUCKET_REMOVE(b);
                     APR_BRIGADE_INSERT_TAIL(io->output, b);
-#if LOG_SCRATCH
-                    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, io->c, APLOGNO(03391)
-                                  "h2_conn_io(%ld): pass bucket(%ld)", 
-                                  io->c->id, (long)b->length);
-#endif
                     continue;
                 }
             }
@@ -408,15 +383,6 @@ apr_status_t h2_conn_io_pass(h2_conn_io
             APR_BRIGADE_INSERT_TAIL(io->output, b);
         }
     }
-    
-    if (status == APR_SUCCESS) {
-        if (!APR_BRIGADE_EMPTY(io->output)) {
-            apr_off_t len = h2_brigade_mem_size(io->output);
-            if (len >= io->pass_threshold) {
-                return pass_output(io, 0, 0);
-            }
-        }
-    }
     return status;
 }
 
diff -up --new-file httpd-2.4.23/modules/http2/h2_conn_io.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_conn_io.h
--- httpd-2.4.23/modules/http2/h2_conn_io.h	2016-05-04 15:58:02.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_conn_io.h	2017-01-21 16:48:17.000000000 +0100
@@ -39,7 +39,8 @@ typedef struct {
     apr_int64_t bytes_written;
     
     int buffer_output;
-    apr_size_t pass_threshold;
+    apr_size_t flush_threshold;
+    unsigned int is_flushed : 1;
     
     char *scratch;
     apr_size_t ssize;
@@ -61,16 +62,15 @@ apr_status_t h2_conn_io_write(h2_conn_io
 apr_status_t h2_conn_io_pass(h2_conn_io *io, apr_bucket_brigade *bb);
 
 /**
- * Append an End-Of-Connection bucket to the output that, once destroyed,
- * will tear down the complete http2 session.
- */
-apr_status_t h2_conn_io_write_eoc(h2_conn_io *io, struct h2_session *session);
-
-/**
  * Pass any buffered data on to the connection output filters.
  * @param io the connection io
  * @param flush if a flush bucket should be appended to any output
  */
 apr_status_t h2_conn_io_flush(h2_conn_io *io);
 
+/**
+ * Check if the buffered amount of data needs flushing.
+ */
+int h2_conn_io_needs_flush(h2_conn_io *io);
+
 #endif /* defined(__mod_h2__h2_conn_io__) */
diff -up --new-file httpd-2.4.23/modules/http2/h2_ctx.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_ctx.c
--- httpd-2.4.23/modules/http2/h2_ctx.c	2016-05-04 15:58:02.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_ctx.c	2016-10-27 18:53:58.000000000 +0200
@@ -27,7 +27,7 @@
 static h2_ctx *h2_ctx_create(const conn_rec *c)
 {
     h2_ctx *ctx = apr_pcalloc(c->pool, sizeof(h2_ctx));
-    AP_DEBUG_ASSERT(ctx);
+    ap_assert(ctx);
     ap_set_module_config(c->conn_config, &http2_module, ctx);
     h2_ctx_server_set(ctx, c->base_server);
     return ctx;
@@ -35,7 +35,7 @@ static h2_ctx *h2_ctx_create(const conn_
 
 void h2_ctx_clear(const conn_rec *c)
 {
-    AP_DEBUG_ASSERT(c);
+    ap_assert(c);
     ap_set_module_config(c->conn_config, &http2_module, NULL);
 }
 
diff -up --new-file httpd-2.4.23/modules/http2/h2_filter.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_filter.c
--- httpd-2.4.23/modules/http2/h2_filter.c	2016-05-23 12:55:29.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_filter.c	2017-04-10 17:04:55.000000000 +0200
@@ -15,14 +15,17 @@
 
 #include <assert.h>
 
+#include <apr_strings.h>
 #include <httpd.h>
 #include <http_core.h>
+#include <http_protocol.h>
 #include <http_log.h>
 #include <http_connection.h>
 #include <scoreboard.h>
 
 #include "h2_private.h"
 #include "h2.h"
+#include "h2_config.h"
 #include "h2_conn_io.h"
 #include "h2_ctx.h"
 #include "h2_mplx.h"
@@ -30,7 +33,8 @@
 #include "h2_task.h"
 #include "h2_stream.h"
 #include "h2_request.h"
-#include "h2_response.h"
+#include "h2_headers.h"
+#include "h2_stream.h"
 #include "h2_session.h"
 #include "h2_util.h"
 #include "h2_version.h"
@@ -40,55 +44,80 @@
 #define UNSET       -1
 #define H2MIN(x,y) ((x) < (y) ? (x) : (y))
 
-static apr_status_t consume_brigade(h2_filter_cin *cin, 
-                                    apr_bucket_brigade *bb, 
-                                    apr_read_type_e block)
+static apr_status_t recv_RAW_DATA(conn_rec *c, h2_filter_cin *cin, 
+                                  apr_bucket *b, apr_read_type_e block)
 {
+    h2_session *session = cin->session;
     apr_status_t status = APR_SUCCESS;
-    apr_size_t readlen = 0;
+    apr_size_t len;
+    const char *data;
+    ssize_t n;
     
-    while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) {
+    status = apr_bucket_read(b, &data, &len, block);
+    
+    while (status == APR_SUCCESS && len > 0) {
+        n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
         
-        apr_bucket* bucket = APR_BRIGADE_FIRST(bb);
-        if (APR_BUCKET_IS_METADATA(bucket)) {
-            /* we do nothing regarding any meta here */
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+                      H2_SSSN_MSG(session, "fed %ld bytes to nghttp2, %ld read"),
+                      (long)len, (long)n);
+        if (n < 0) {
+            if (nghttp2_is_fatal((int)n)) {
+                h2_session_event(session, H2_SESSION_EV_PROTO_ERROR, 
+                                 (int)n, nghttp2_strerror((int)n));
+                status = APR_EGENERAL;
+            }
         }
         else {
-            const char *bucket_data = NULL;
-            apr_size_t bucket_length = 0;
-            status = apr_bucket_read(bucket, &bucket_data,
-                                     &bucket_length, block);
-            
-            if (status == APR_SUCCESS && bucket_length > 0) {
-                apr_size_t consumed = 0;
-
-                status = cin->cb(cin->cb_ctx, bucket_data, bucket_length, &consumed);
-                if (status == APR_SUCCESS && bucket_length > consumed) {
-                    /* We have data left in the bucket. Split it. */
-                    status = apr_bucket_split(bucket, consumed);
-                }
-                readlen += consumed;
-                cin->start_read = apr_time_now();
+            session->io.bytes_read += n;
+            if (len <= n) {
+                break;
             }
+            len -= n;
+            data += n;
+        }
+    }
+    
+    return status;
+}
+
+static apr_status_t recv_RAW_brigade(conn_rec *c, h2_filter_cin *cin, 
+                                     apr_bucket_brigade *bb, 
+                                     apr_read_type_e block)
+{
+    apr_status_t status = APR_SUCCESS;
+    apr_bucket* b;
+    int consumed = 0;
+    
+    h2_util_bb_log(c, c->id, APLOG_TRACE2, "RAW_in", bb);
+    while (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(bb)) {
+        b = APR_BRIGADE_FIRST(bb);
+
+        if (APR_BUCKET_IS_METADATA(b)) {
+            /* nop */
+        }
+        else {
+            status = recv_RAW_DATA(c, cin, b, block);
         }
-        apr_bucket_delete(bucket);
+        consumed = 1;
+        apr_bucket_delete(b);
     }
     
-    if (readlen == 0 && status == APR_SUCCESS && block == APR_NONBLOCK_READ) {
+    if (!consumed && status == APR_SUCCESS && block == APR_NONBLOCK_READ) {
         return APR_EAGAIN;
     }
     return status;
 }
 
-h2_filter_cin *h2_filter_cin_create(apr_pool_t *p, h2_filter_cin_cb *cb, void *ctx)
+h2_filter_cin *h2_filter_cin_create(h2_session *session)
 {
     h2_filter_cin *cin;
     
-    cin = apr_pcalloc(p, sizeof(*cin));
-    cin->pool      = p;
-    cin->cb        = cb;
-    cin->cb_ctx    = ctx;
-    cin->start_read = UNSET;
+    cin = apr_pcalloc(session->pool, sizeof(*cin));
+    if (!cin) {
+        return NULL;
+    }
+    cin->session = session;
     return cin;
 }
 
@@ -106,11 +135,14 @@ apr_status_t h2_filter_core_input(ap_fil
     h2_filter_cin *cin = f->ctx;
     apr_status_t status = APR_SUCCESS;
     apr_interval_time_t saved_timeout = UNSET;
+    const int trace1 = APLOGctrace1(f->c);
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                  "core_input(%ld): read, %s, mode=%d, readbytes=%ld", 
-                  (long)f->c->id, (block == APR_BLOCK_READ)? "BLOCK_READ" : "NONBLOCK_READ", 
-                  mode, (long)readbytes);
+    if (trace1) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+                      "h2_session(%ld): read, %s, mode=%d, readbytes=%ld", 
+                      (long)f->c->id, (block == APR_BLOCK_READ)? 
+                      "BLOCK_READ" : "NONBLOCK_READ", mode, (long)readbytes);
+    }
     
     if (mode == AP_MODE_INIT || mode == AP_MODE_SPECULATIVE) {
         return ap_get_brigade(f->next, brigade, mode, block, readbytes);
@@ -121,20 +153,16 @@ apr_status_t h2_filter_core_input(ap_fil
     }
     
     if (!cin->bb) {
-        cin->bb = apr_brigade_create(cin->pool, f->c->bucket_alloc);
+        cin->bb = apr_brigade_create(cin->session->pool, f->c->bucket_alloc);
     }
 
     if (!cin->socket) {
         cin->socket = ap_get_conn_socket(f->c);
     }
     
-    cin->start_read = apr_time_now();
     if (APR_BRIGADE_EMPTY(cin->bb)) {
         /* We only do a blocking read when we have no streams to process. So,
          * in httpd scoreboard lingo, we are in a KEEPALIVE connection state.
-         * When reading non-blocking, we do have streams to process and update
-         * child with NULL request. That way, any current request information
-         * in the scoreboard is preserved.
          */
         if (block == APR_BLOCK_READ) {
             if (cin->timeout > 0) {
@@ -151,17 +179,19 @@ apr_status_t h2_filter_core_input(ap_fil
     
     switch (status) {
         case APR_SUCCESS:
-            status = consume_brigade(cin, cin->bb, block);
+            status = recv_RAW_brigade(f->c, cin, cin->bb, block);
             break;
         case APR_EOF:
         case APR_EAGAIN:
         case APR_TIMEUP:
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
-                          "core_input(%ld): read", (long)f->c->id);
+            if (trace1) {
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+                              "h2_session(%ld): read", f->c->id);
+            }
             break;
         default:
             ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, f->c, APLOGNO(03046)
-                          "h2_conn_io: error reading");
+                          "h2_session(%ld): error reading", f->c->id);
             break;
     }
     return status;
@@ -171,30 +201,92 @@ apr_status_t h2_filter_core_input(ap_fil
  * http2 connection status handler + stream out source
  ******************************************************************************/
 
-static const char *H2_SOS_H2_STATUS = "http2-status";
+typedef struct {
+    apr_bucket_refcount refcount;
+    h2_bucket_event_cb *cb;
+    void *ctx;
+} h2_bucket_observer;
+ 
+static apr_status_t bucket_read(apr_bucket *b, const char **str,
+                                apr_size_t *len, apr_read_type_e block)
+{
+    (void)b;
+    (void)block;
+    *str = NULL;
+    *len = 0;
+    return APR_SUCCESS;
+}
 
-int h2_filter_h2_status_handler(request_rec *r)
+static void bucket_destroy(void *data)
 {
-    h2_ctx *ctx = h2_ctx_rget(r);
-    h2_task *task;
-    
-    if (strcmp(r->handler, "http2-status")) {
-        return DECLINED;
+    h2_bucket_observer *h = data;
+    if (apr_bucket_shared_destroy(h)) {
+        if (h->cb) {
+            h->cb(h->ctx, H2_BUCKET_EV_BEFORE_DESTROY, NULL);
+        }
+        apr_bucket_free(h);
     }
-    if (r->method_number != M_GET) {
-        return DECLINED;
+}
+
+apr_bucket * h2_bucket_observer_make(apr_bucket *b, h2_bucket_event_cb *cb,
+                                 void *ctx)
+{
+    h2_bucket_observer *br;
+
+    br = apr_bucket_alloc(sizeof(*br), b->list);
+    br->cb = cb;
+    br->ctx = ctx;
+
+    b = apr_bucket_shared_make(b, br, 0, 0);
+    b->type = &h2_bucket_type_observer;
+    return b;
+} 
+
+apr_bucket * h2_bucket_observer_create(apr_bucket_alloc_t *list, 
+                                       h2_bucket_event_cb *cb, void *ctx)
+{
+    apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+
+    APR_BUCKET_INIT(b);
+    b->free = apr_bucket_free;
+    b->list = list;
+    b = h2_bucket_observer_make(b, cb, ctx);
+    return b;
+}
+                                       
+apr_status_t h2_bucket_observer_fire(apr_bucket *b, h2_bucket_event event)
+{
+    if (H2_BUCKET_IS_OBSERVER(b)) {
+        h2_bucket_observer *l = (h2_bucket_observer *)b->data; 
+        return l->cb(l->ctx, event, b);
     }
+    return APR_EINVAL;
+}
 
-    task = ctx? h2_ctx_get_task(ctx) : NULL;
-    if (task) {
-        /* We need to handle the actual output on the main thread, as
-         * we need to access h2_session information. */
-        apr_table_setn(r->notes, H2_RESP_SOS_NOTE, H2_SOS_H2_STATUS);
-        apr_table_setn(r->headers_out, "Content-Type", "application/json");
-        r->status = 200;
-        return DONE;
+const apr_bucket_type_t h2_bucket_type_observer = {
+    "H2OBS", 5, APR_BUCKET_METADATA,
+    bucket_destroy,
+    bucket_read,
+    apr_bucket_setaside_noop,
+    apr_bucket_split_notimpl,
+    apr_bucket_shared_copy
+};
+
+apr_bucket *h2_bucket_observer_beam(struct h2_bucket_beam *beam,
+                                    apr_bucket_brigade *dest,
+                                    const apr_bucket *src)
+{
+    if (H2_BUCKET_IS_OBSERVER(src)) {
+        h2_bucket_observer *l = (h2_bucket_observer *)src->data; 
+        apr_bucket *b = h2_bucket_observer_create(dest->bucket_alloc, 
+                                                  l->cb, l->ctx);
+        APR_BRIGADE_INSERT_TAIL(dest, b);
+        l->cb = NULL;
+        l->ctx = NULL;
+        h2_bucket_observer_fire(b, H2_BUCKET_EV_BEFORE_MASTER_SEND);
+        return b;
     }
-    return DECLINED;
+    return NULL;
 }
 
 static apr_status_t bbout(apr_bucket_brigade *bb, const char *fmt, ...)
@@ -209,82 +301,260 @@ static apr_status_t bbout(apr_bucket_bri
     return rv;
 }
 
-static apr_status_t h2_status_stream_filter(h2_stream *stream)
+static void add_settings(apr_bucket_brigade *bb, h2_session *s, int last) 
 {
-    h2_session *session = stream->session;
-    h2_mplx *mplx = session->mplx;
-    conn_rec *c = session->c;
-    h2_push_diary *diary;
-    apr_bucket_brigade *bb;
-    apr_status_t status;
+    h2_mplx *m = s->mplx;
     
-    if (!stream->response) {
-        return APR_EINVAL;
-    }
-    
-    if (!stream->buffer) {
-        stream->buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
-    }
-    bb = stream->buffer;
+    bbout(bb, "  \"settings\": {\n");
+    bbout(bb, "    \"SETTINGS_MAX_CONCURRENT_STREAMS\": %d,\n", m->max_streams); 
+    bbout(bb, "    \"SETTINGS_MAX_FRAME_SIZE\": %d,\n", 16*1024); 
+    bbout(bb, "    \"SETTINGS_INITIAL_WINDOW_SIZE\": %d,\n",
+          h2_config_geti(s->config, H2_CONF_WIN_SIZE));
+    bbout(bb, "    \"SETTINGS_ENABLE_PUSH\": %d\n", h2_session_push_enabled(s)); 
+    bbout(bb, "  }%s\n", last? "" : ",");
+}
+
+static void add_peer_settings(apr_bucket_brigade *bb, h2_session *s, int last) 
+{
+    bbout(bb, "  \"peerSettings\": {\n");
+    bbout(bb, "    \"SETTINGS_MAX_CONCURRENT_STREAMS\": %d,\n", 
+        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS)); 
+    bbout(bb, "    \"SETTINGS_MAX_FRAME_SIZE\": %d,\n", 
+        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_FRAME_SIZE)); 
+    bbout(bb, "    \"SETTINGS_INITIAL_WINDOW_SIZE\": %d,\n", 
+        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE)); 
+    bbout(bb, "    \"SETTINGS_ENABLE_PUSH\": %d,\n", 
+        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_ENABLE_PUSH)); 
+    bbout(bb, "    \"SETTINGS_HEADER_TABLE_SIZE\": %d,\n", 
+        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_HEADER_TABLE_SIZE)); 
+    bbout(bb, "    \"SETTINGS_MAX_HEADER_LIST_SIZE\": %d\n", 
+        nghttp2_session_get_remote_settings(s->ngh2, NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE)); 
+    bbout(bb, "  }%s\n", last? "" : ",");
+}
+
+typedef struct {
+    apr_bucket_brigade *bb;
+    h2_session *s;
+    int idx;
+} stream_ctx_t;
+
+static int add_stream(h2_stream *stream, void *ctx)
+{
+    stream_ctx_t *x = ctx;
+    int32_t flowIn, flowOut;
     
-    apr_table_unset(stream->response->headers, "Content-Length");
-    stream->response->content_length = -1;
+    flowIn = nghttp2_session_get_stream_effective_local_window_size(x->s->ngh2, stream->id); 
+    flowOut = nghttp2_session_get_stream_remote_window_size(x->s->ngh2, stream->id);
+    bbout(x->bb, "%s\n    \"%d\": {\n", (x->idx? "," : ""), stream->id);
+    bbout(x->bb, "    \"state\": \"%s\",\n", h2_stream_state_str(stream));
+    bbout(x->bb, "    \"created\": %f,\n", ((double)stream->created)/APR_USEC_PER_SEC);
+    bbout(x->bb, "    \"flowIn\": %d,\n", flowIn);
+    bbout(x->bb, "    \"flowOut\": %d,\n", flowOut);
+    bbout(x->bb, "    \"dataIn\": %"APR_UINT64_T_FMT",\n", stream->in_data_octets);  
+    bbout(x->bb, "    \"dataOut\": %"APR_UINT64_T_FMT"\n", stream->out_data_octets);  
+    bbout(x->bb, "    }");
+    
+    ++x->idx;
+    return 1;
+} 
+
+static void add_streams(apr_bucket_brigade *bb, h2_session *s, int last) 
+{
+    stream_ctx_t x;
     
-    bbout(bb, "{\n");
-    bbout(bb, "  \"HTTP2\": \"on\",\n");
-    bbout(bb, "  \"H2PUSH\": \"%s\",\n", h2_session_push_enabled(session)? "on" : "off");
-    bbout(bb, "  \"mod_http2_version\": \"%s\",\n", MOD_HTTP2_VERSION);
-    bbout(bb, "  \"session_id\": %ld,\n", (long)session->id);
-    bbout(bb, "  \"streams_max\": %d,\n", (int)session->max_stream_count);
-    bbout(bb, "  \"this_stream\": %d,\n", stream->id);
-    bbout(bb, "  \"streams_open\": %d,\n", (int)h2_ihash_count(session->streams));
-    bbout(bb, "  \"max_stream_started\": %d,\n", mplx->max_stream_started);
-    bbout(bb, "  \"requests_received\": %d,\n", session->remote.emitted_count);
-    bbout(bb, "  \"responses_submitted\": %d,\n", session->responses_submitted);
-    bbout(bb, "  \"streams_reset\": %d, \n", session->streams_reset);
-    bbout(bb, "  \"pushes_promised\": %d,\n", session->pushes_promised);
-    bbout(bb, "  \"pushes_submitted\": %d,\n", session->pushes_submitted);
-    bbout(bb, "  \"pushes_reset\": %d,\n", session->pushes_reset);
+    x.bb = bb;
+    x.s = s;
+    x.idx = 0;
+    bbout(bb, "  \"streams\": {");
+    h2_mplx_stream_do(s->mplx, add_stream, &x);
+    bbout(bb, "\n  }%s\n", last? "" : ",");
+}
+
+static void add_push(apr_bucket_brigade *bb, h2_session *s, 
+                     h2_stream *stream, int last) 
+{
+    h2_push_diary *diary;
+    apr_status_t status;
     
-    diary = session->push_diary;
+    bbout(bb, "    \"push\": {\n");
+    diary = s->push_diary;
     if (diary) {
         const char *data;
         const char *base64_digest;
         apr_size_t len;
         
-        status = h2_push_diary_digest_get(diary, stream->pool, 256, 
-                                          stream->request->authority, &data, &len);
+        status = h2_push_diary_digest_get(diary, bb->p, 256, 
+                                          stream->request->authority, 
+                                          &data, &len);
         if (status == APR_SUCCESS) {
-            base64_digest = h2_util_base64url_encode(data, len, stream->pool);
-            bbout(bb, "  \"cache_digest\": \"%s\",\n", base64_digest);
-        }
-        
-        /* try the reverse for testing purposes */
-        status = h2_push_diary_digest_set(diary, stream->request->authority, data, len);
-        if (status == APR_SUCCESS) {
-            status = h2_push_diary_digest_get(diary, stream->pool, 256, 
-                                              stream->request->authority, &data, &len);
-            if (status == APR_SUCCESS) {
-                base64_digest = h2_util_base64url_encode(data, len, stream->pool);
-                bbout(bb, "  \"cache_digest^2\": \"%s\",\n", base64_digest);
-            }
+            base64_digest = h2_util_base64url_encode(data, len, bb->p);
+            bbout(bb, "      \"cacheDigest\": \"%s\",\n", base64_digest);
         }
     }
-    bbout(bb, "  \"frames_received\": %ld,\n", (long)session->frames_received);
-    bbout(bb, "  \"frames_sent\": %ld,\n", (long)session->frames_sent);
-    bbout(bb, "  \"bytes_received\": %"APR_UINT64_T_FMT",\n", session->io.bytes_read);
-    bbout(bb, "  \"bytes_sent\": %"APR_UINT64_T_FMT"\n", session->io.bytes_written);
+    bbout(bb, "      \"promises\": %d,\n", s->pushes_promised);
+    bbout(bb, "      \"submits\": %d,\n", s->pushes_submitted);
+    bbout(bb, "      \"resets\": %d\n", s->pushes_reset);
+    bbout(bb, "    }%s\n", last? "" : ",");
+}
+
+static void add_in(apr_bucket_brigade *bb, h2_session *s, int last) 
+{
+    bbout(bb, "    \"in\": {\n");
+    bbout(bb, "      \"requests\": %d,\n", s->remote.emitted_count);
+    bbout(bb, "      \"resets\": %d, \n", s->streams_reset);
+    bbout(bb, "      \"frames\": %ld,\n", (long)s->frames_received);
+    bbout(bb, "      \"octets\": %"APR_UINT64_T_FMT"\n", s->io.bytes_read);
+    bbout(bb, "    }%s\n", last? "" : ",");
+}
+
+static void add_out(apr_bucket_brigade *bb, h2_session *s, int last) 
+{
+    bbout(bb, "    \"out\": {\n");
+    bbout(bb, "      \"responses\": %d,\n", s->responses_submitted);
+    bbout(bb, "      \"frames\": %ld,\n", (long)s->frames_sent);
+    bbout(bb, "      \"octets\": %"APR_UINT64_T_FMT"\n", s->io.bytes_written);
+    bbout(bb, "    }%s\n", last? "" : ",");
+}
+
+static void add_stats(apr_bucket_brigade *bb, h2_session *s, 
+                     h2_stream *stream, int last) 
+{
+    bbout(bb, "  \"stats\": {\n");
+    add_in(bb, s, 0);
+    add_out(bb, s, 0);
+    add_push(bb, s, stream, 1);
+    bbout(bb, "  }%s\n", last? "" : ",");
+}
+
+static apr_status_t h2_status_insert(h2_task *task, apr_bucket *b)
+{
+    conn_rec *c = task->c->master;
+    h2_ctx *h2ctx = h2_ctx_get(c, 0);
+    h2_session *session;
+    h2_stream *stream;
+    apr_bucket_brigade *bb;
+    apr_bucket *e;
+    int32_t connFlowIn, connFlowOut;
+    
+    
+    if (!h2ctx || (session = h2_ctx_session_get(h2ctx)) == NULL) {
+        return APR_SUCCESS;
+    }
+    
+    stream = h2_session_stream_get(session, task->stream_id);
+    if (!stream) {
+        /* stream already done */
+        return APR_SUCCESS;
+    }
+    
+    bb = apr_brigade_create(stream->pool, c->bucket_alloc);
+    
+    connFlowIn = nghttp2_session_get_effective_local_window_size(session->ngh2); 
+    connFlowOut = nghttp2_session_get_remote_window_size(session->ngh2);
+     
+    bbout(bb, "{\n");
+    bbout(bb, "  \"version\": \"draft-01\",\n");
+    add_settings(bb, session, 0);
+    add_peer_settings(bb, session, 0);
+    bbout(bb, "  \"connFlowIn\": %d,\n", connFlowIn);
+    bbout(bb, "  \"connFlowOut\": %d,\n", connFlowOut);
+    bbout(bb, "  \"sentGoAway\": %d,\n", session->local.shutdown);
+
+    add_streams(bb, session, 0);
+    
+    add_stats(bb, session, stream, 1);
     bbout(bb, "}\n");
     
+    while ((e = APR_BRIGADE_FIRST(bb)) != APR_BRIGADE_SENTINEL(bb)) {
+        APR_BUCKET_REMOVE(e);
+        APR_BUCKET_INSERT_AFTER(b, e);
+        b = e;
+    }
+    apr_brigade_destroy(bb);
+    
     return APR_SUCCESS;
 }
 
-apr_status_t h2_stream_filter(h2_stream *stream)
+static apr_status_t status_event(void *ctx, h2_bucket_event event, 
+                                 apr_bucket *b)
 {
-    const char *fname = stream->response? stream->response->sos_filter : NULL; 
-    if (fname && !strcmp(H2_SOS_H2_STATUS, fname)) {
-        return h2_status_stream_filter(stream);
+    h2_task *task = ctx;
+    
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, task->c->master, 
+                  "status_event(%s): %d", task->id, event);
+    switch (event) {
+        case H2_BUCKET_EV_BEFORE_MASTER_SEND:
+            h2_status_insert(task, b);
+            break;
+        default:
+            break;
     }
     return APR_SUCCESS;
 }
 
+int h2_filter_h2_status_handler(request_rec *r)
+{
+    h2_ctx *ctx = h2_ctx_rget(r);
+    conn_rec *c = r->connection;
+    h2_task *task;
+    apr_bucket_brigade *bb;
+    apr_bucket *b;
+    apr_status_t status;
+    
+    if (strcmp(r->handler, "http2-status")) {
+        return DECLINED;
+    }
+    if (r->method_number != M_GET && r->method_number != M_POST) {
+        return DECLINED;
+    }
+
+    task = ctx? h2_ctx_get_task(ctx) : NULL;
+    if (task) {
+
+        if ((status = ap_discard_request_body(r)) != OK) {
+            return status;
+        }
+        
+        /* We need to handle the actual output on the main thread, as
+         * we need to access h2_session information. */
+        r->status = 200;
+        r->clength = -1;
+        r->chunked = 1;
+        apr_table_unset(r->headers_out, "Content-Length");
+        ap_set_content_type(r, "application/json");
+        apr_table_setn(r->notes, H2_FILTER_DEBUG_NOTE, "on");
+
+        bb = apr_brigade_create(r->pool, c->bucket_alloc);
+        b = h2_bucket_observer_create(c->bucket_alloc, status_event, task);
+        APR_BRIGADE_INSERT_TAIL(bb, b);
+        b = apr_bucket_eos_create(c->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(bb, b);
+
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                      "status_handler(%s): checking for incoming trailers", 
+                      task->id);
+        if (r->trailers_in && !apr_is_empty_table(r->trailers_in)) {
+            ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                          "status_handler(%s): seeing incoming trailers", 
+                          task->id);
+            apr_table_setn(r->trailers_out, "h2-trailers-in", 
+                           apr_itoa(r->pool, 1));
+        }
+        
+        status = ap_pass_brigade(r->output_filters, bb);
+        if (status == APR_SUCCESS
+            || r->status != HTTP_OK
+            || c->aborted) {
+            return OK;
+        }
+        else {
+            /* no way to know what type of error occurred */
+            ap_log_rerror(APLOG_MARK, APLOG_TRACE1, status, r,
+                          "status_handler(%s): ap_pass_brigade failed", 
+                          task->id);
+            return AP_FILTER_ERROR;
+        }
+    }
+    return DECLINED;
+}
+
diff -up --new-file httpd-2.4.23/modules/http2/h2_filter.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_filter.h
--- httpd-2.4.23/modules/http2/h2_filter.h	2016-05-23 12:55:29.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_filter.h	2017-03-31 21:41:01.000000000 +0200
@@ -16,24 +16,21 @@
 #ifndef __mod_h2__h2_filter__
 #define __mod_h2__h2_filter__
 
+struct h2_bucket_beam;
+struct h2_headers;
 struct h2_stream;
 struct h2_session;
 
-typedef apr_status_t h2_filter_cin_cb(void *ctx, 
-                                      const char *data, apr_size_t len,
-                                      apr_size_t *readlen);
-
 typedef struct h2_filter_cin {
     apr_pool_t *pool;
-    apr_bucket_brigade *bb;
-    h2_filter_cin_cb *cb;
-    void *cb_ctx;
     apr_socket_t *socket;
     apr_interval_time_t timeout;
-    apr_time_t start_read;
+    apr_bucket_brigade *bb;
+    struct h2_session *session;
+    apr_bucket *cur;
 } h2_filter_cin;
 
-h2_filter_cin *h2_filter_cin_create(apr_pool_t *p, h2_filter_cin_cb *cb, void *ctx);
+h2_filter_cin *h2_filter_cin_create(struct h2_session *session);
 
 void h2_filter_cin_timeout_set(h2_filter_cin *cin, apr_interval_time_t timeout);
 
@@ -43,9 +40,33 @@ apr_status_t h2_filter_core_input(ap_fil
                                   apr_read_type_e block,
                                   apr_off_t readbytes);
 
-#define H2_RESP_SOS_NOTE     "h2-sos-filter"
+/******* observer bucket ******************************************************/
+
+typedef enum {
+    H2_BUCKET_EV_BEFORE_DESTROY,
+    H2_BUCKET_EV_BEFORE_MASTER_SEND
+} h2_bucket_event;
+
+extern const apr_bucket_type_t h2_bucket_type_observer;
+
+typedef apr_status_t h2_bucket_event_cb(void *ctx, h2_bucket_event event, apr_bucket *b);
+
+#define H2_BUCKET_IS_OBSERVER(e)     (e->type == &h2_bucket_type_observer)
+
+apr_bucket * h2_bucket_observer_make(apr_bucket *b, h2_bucket_event_cb *cb, 
+                                     void *ctx); 
+
+apr_bucket * h2_bucket_observer_create(apr_bucket_alloc_t *list, 
+                                       h2_bucket_event_cb *cb, void *ctx); 
+                                       
+apr_status_t h2_bucket_observer_fire(apr_bucket *b, h2_bucket_event event);
+
+apr_bucket *h2_bucket_observer_beam(struct h2_bucket_beam *beam,
+                                    apr_bucket_brigade *dest,
+                                    const apr_bucket *src);
+
+/******* /.well-known/h2/state handler ****************************************/
 
-apr_status_t h2_stream_filter(struct h2_stream *stream);
 int h2_filter_h2_status_handler(request_rec *r);
 
 #endif /* __mod_h2__h2_filter__ */
diff -up --new-file httpd-2.4.23/modules/http2/h2_from_h1.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_from_h1.c
--- httpd-2.4.23/modules/http2/h2_from_h1.c	2016-05-18 17:10:20.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_from_h1.c	2017-05-05 15:23:48.000000000 +0200
@@ -16,6 +16,7 @@
 #include <assert.h>
 #include <stdio.h>
 
+#include <apr_date.h>
 #include <apr_lib.h>
 #include <apr_strings.h>
 
@@ -28,190 +29,12 @@
 #include <util_time.h>
 
 #include "h2_private.h"
-#include "h2_response.h"
+#include "h2_headers.h"
 #include "h2_from_h1.h"
 #include "h2_task.h"
 #include "h2_util.h"
 
 
-static void set_state(h2_from_h1 *from_h1, h2_from_h1_state_t state);
-
-h2_from_h1 *h2_from_h1_create(int stream_id, apr_pool_t *pool)
-{
-    h2_from_h1 *from_h1 = apr_pcalloc(pool, sizeof(h2_from_h1));
-    if (from_h1) {
-        from_h1->stream_id = stream_id;
-        from_h1->pool = pool;
-        from_h1->state = H2_RESP_ST_STATUS_LINE;
-        from_h1->hlines = apr_array_make(pool, 10, sizeof(char *));
-    }
-    return from_h1;
-}
-
-static void set_state(h2_from_h1 *from_h1, h2_from_h1_state_t state)
-{
-    if (from_h1->state != state) {
-        from_h1->state = state;
-    }
-}
-
-h2_response *h2_from_h1_get_response(h2_from_h1 *from_h1)
-{
-    return from_h1->response;
-}
-
-static apr_status_t make_h2_headers(h2_from_h1 *from_h1, request_rec *r)
-{
-    from_h1->response = h2_response_create(from_h1->stream_id, 0,
-                                           from_h1->http_status, 
-                                           from_h1->hlines,
-                                           r->notes,
-                                           from_h1->pool);
-    from_h1->content_length = from_h1->response->content_length;
-    from_h1->chunked = r->chunked;
-    
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, r->connection, APLOGNO(03197)
-                  "h2_from_h1(%d): converted headers, content-length: %d"
-                  ", chunked=%d",
-                  from_h1->stream_id, (int)from_h1->content_length, 
-                  (int)from_h1->chunked);
-    
-    set_state(from_h1, ((from_h1->chunked || from_h1->content_length > 0)?
-                        H2_RESP_ST_BODY : H2_RESP_ST_DONE));
-    /* We are ready to be sent to the client */
-    return APR_SUCCESS;
-}
-
-static apr_status_t parse_header(h2_from_h1 *from_h1, ap_filter_t* f, 
-                                 char *line) {
-    (void)f;
-    
-    if (line[0] == ' ' || line[0] == '\t') {
-        char **plast;
-        /* continuation line from the header before this */
-        while (line[0] == ' ' || line[0] == '\t') {
-            ++line;
-        }
-        
-        plast = apr_array_pop(from_h1->hlines);
-        if (plast == NULL) {
-            /* not well formed */
-            return APR_EINVAL;
-        }
-        APR_ARRAY_PUSH(from_h1->hlines, const char*) = apr_psprintf(from_h1->pool, "%s %s", *plast, line);
-    }
-    else {
-        /* new header line */
-        APR_ARRAY_PUSH(from_h1->hlines, const char*) = apr_pstrdup(from_h1->pool, line);
-    }
-    return APR_SUCCESS;
-}
-
-static apr_status_t get_line(h2_from_h1 *from_h1, apr_bucket_brigade *bb,
-                             ap_filter_t* f, char *line, apr_size_t len)
-{
-    apr_status_t status;
-    if (!from_h1->bb) {
-        from_h1->bb = apr_brigade_create(from_h1->pool, f->c->bucket_alloc);
-    }
-    else {
-        apr_brigade_cleanup(from_h1->bb);                
-    }
-    status = apr_brigade_split_line(from_h1->bb, bb, 
-                                                 APR_BLOCK_READ, 
-                                                 HUGE_STRING_LEN);
-    if (status == APR_SUCCESS) {
-        --len;
-        status = apr_brigade_flatten(from_h1->bb, line, &len);
-        if (status == APR_SUCCESS) {
-            /* we assume a non-0 containing line and remove
-             * trailing crlf. */
-            line[len] = '\0';
-            if (len >= 2 && !strcmp(H2_CRLF, line + len - 2)) {
-                len -= 2;
-                line[len] = '\0';
-            }
-            
-            apr_brigade_cleanup(from_h1->bb);
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                          "h2_from_h1(%d): read line: %s",
-                          from_h1->stream_id, line);
-        }
-    }
-    return status;
-}
-
-apr_status_t h2_from_h1_read_response(h2_from_h1 *from_h1, ap_filter_t* f,
-                                      apr_bucket_brigade* bb)
-{
-    apr_status_t status = APR_SUCCESS;
-    char line[HUGE_STRING_LEN];
-    
-    if ((from_h1->state == H2_RESP_ST_BODY) 
-        || (from_h1->state == H2_RESP_ST_DONE)) {
-        if (from_h1->chunked) {
-            /* The httpd core HTTP_HEADER filter has or will install the 
-             * "CHUNK" output transcode filter, which appears further down 
-             * the filter chain. We do not want it for HTTP/2.
-             * Once we successfully deinstalled it, this filter has no
-             * further function and we remove it.
-             */
-            status = ap_remove_output_filter_byhandle(f->r->output_filters, 
-                                                      "CHUNK");
-            if (status == APR_SUCCESS) {
-                ap_remove_output_filter(f);
-            }
-        }
-        
-        return ap_pass_brigade(f->next, bb);
-    }
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                  "h2_from_h1(%d): read_response", from_h1->stream_id);
-    
-    while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
-        
-        switch (from_h1->state) {
-                
-            case H2_RESP_ST_STATUS_LINE:
-            case H2_RESP_ST_HEADERS:
-                status = get_line(from_h1, bb, f, line, sizeof(line));
-                if (status != APR_SUCCESS) {
-                    return status;
-                }
-                if (from_h1->state == H2_RESP_ST_STATUS_LINE) {
-                    /* instead of parsing, just take it directly */
-                    from_h1->http_status = f->r->status;
-                    from_h1->state = H2_RESP_ST_HEADERS;
-                }
-                else if (line[0] == '\0') {
-                    /* end of headers, create the h2_response and
-                     * pass the rest of the brigade down the filter
-                     * chain.
-                     */
-                    status = make_h2_headers(from_h1, f->r);
-                    if (from_h1->bb) {
-                        apr_brigade_destroy(from_h1->bb);
-                        from_h1->bb = NULL;
-                    }
-                    if (!APR_BRIGADE_EMPTY(bb)) {
-                        return ap_pass_brigade(f->next, bb);
-                    }
-                }
-                else {
-                    status = parse_header(from_h1, f, line);
-                }
-                break;
-                
-            default:
-                return ap_pass_brigade(f->next, bb);
-        }
-        
-    }
-    
-    return status;
-}
-
 /* This routine is called by apr_table_do and merges all instances of
  * the passed field values into a single array that will be further
  * processed by some later routine.  Originally intended to help split
@@ -280,8 +103,7 @@ static void fix_vary(request_rec *r)
      * its comma-separated fieldname values, and then add them to varies
      * if not already present in the array.
      */
-    apr_table_do((int (*)(void *, const char *, const char *))uniq_field_values,
-                 (void *) varies, r->headers_out, "Vary", NULL);
+    apr_table_do(uniq_field_values, varies, r->headers_out, "Vary", NULL);
     
     /* If we found any, replace old Vary fields with unique-ified value */
     
@@ -291,8 +113,8 @@ static void fix_vary(request_rec *r)
     }
 }
 
-void h2_from_h1_set_basic_http_header(apr_table_t *headers, request_rec *r,
-                                      apr_pool_t *pool)
+static void set_basic_http_header(apr_table_t *headers, request_rec *r,
+                                  apr_pool_t *pool)
 {
     char *date = NULL;
     const char *proxy_date = NULL;
@@ -345,7 +167,7 @@ static int copy_header(void *ctx, const
     return 1;
 }
 
-static h2_response *create_response(h2_from_h1 *from_h1, request_rec *r)
+static h2_headers *create_response(h2_task *task, request_rec *r)
 {
     const char *clheader;
     const char *ctype;
@@ -450,10 +272,9 @@ static h2_response *create_response(h2_f
     
     headers = apr_table_make(r->pool, 10);
     
-    h2_from_h1_set_basic_http_header(headers, r, r->pool);
+    set_basic_http_header(headers, r, r->pool);
     if (r->status == HTTP_NOT_MODIFIED) {
-        apr_table_do((int (*)(void *, const char *, const char *)) copy_header,
-                     (void *) headers, r->headers_out,
+        apr_table_do(copy_header, headers, r->headers_out,
                      "ETag",
                      "Content-Location",
                      "Expires",
@@ -467,118 +288,570 @@ static h2_response *create_response(h2_f
                      NULL);
     }
     else {
-        apr_table_do((int (*)(void *, const char *, const char *)) copy_header,
-                     (void *) headers, r->headers_out, NULL);
+        apr_table_do(copy_header, headers, r->headers_out, NULL);
     }
     
-    return h2_response_rcreate(from_h1->stream_id, r, headers, r->pool);
+    return h2_headers_rcreate(r, r->status, headers, r->pool);
+}
+
+typedef enum {
+    H2_RP_STATUS_LINE,
+    H2_RP_HEADER_LINE,
+    H2_RP_DONE
+} h2_rp_state_t;
+
+typedef struct h2_response_parser {
+    h2_rp_state_t state;
+    h2_task *task;
+    int http_status;
+    apr_array_header_t *hlines;
+    apr_bucket_brigade *tmp;
+} h2_response_parser;
+
+static apr_status_t parse_header(h2_response_parser *parser, char *line) {
+    const char *hline;
+    if (line[0] == ' ' || line[0] == '\t') {
+        char **plast;
+        /* continuation line from the header before this */
+        while (line[0] == ' ' || line[0] == '\t') {
+            ++line;
+        }
+        
+        plast = apr_array_pop(parser->hlines);
+        if (plast == NULL) {
+            /* not well formed */
+            return APR_EINVAL;
+        }
+        hline = apr_psprintf(parser->task->pool, "%s %s", *plast, line);
+    }
+    else {
+        /* new header line */
+        hline = apr_pstrdup(parser->task->pool, line);
+    }
+    APR_ARRAY_PUSH(parser->hlines, const char*) = hline; 
+    return APR_SUCCESS;
 }
 
-apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+static apr_status_t get_line(h2_response_parser *parser, apr_bucket_brigade *bb, 
+                             char *line, apr_size_t len)
 {
-    h2_task *task = f->ctx;
-    h2_from_h1 *from_h1 = task->output.from_h1;
-    request_rec *r = f->r;
-    apr_bucket *b;
-    ap_bucket_error *eb = NULL;
+    h2_task *task = parser->task;
+    apr_status_t status;
+    
+    if (!parser->tmp) {
+        parser->tmp = apr_brigade_create(task->pool, task->c->bucket_alloc);
+    }
+    status = apr_brigade_split_line(parser->tmp, bb, APR_BLOCK_READ, 
+                                    HUGE_STRING_LEN);
+    if (status == APR_SUCCESS) {
+        --len;
+        status = apr_brigade_flatten(parser->tmp, line, &len);
+        if (status == APR_SUCCESS) {
+            /* we assume a non-0 containing line and remove trailing crlf. */
+            line[len] = '\0';
+            if (len >= 2 && !strcmp(H2_CRLF, line + len - 2)) {
+                len -= 2;
+                line[len] = '\0';
+                apr_brigade_cleanup(parser->tmp);
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
+                              "h2_task(%s): read response line: %s", 
+                              task->id, line);
+            }
+            else {
+                /* this does not look like a complete line yet */
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c,
+                              "h2_task(%s): read response, incomplete line: %s", 
+                              task->id, line);
+                return APR_EAGAIN;
+            }
+        }
+    }
+    apr_brigade_cleanup(parser->tmp);
+    return status;
+}
 
-    AP_DEBUG_ASSERT(from_h1 != NULL);
+static apr_table_t *make_table(h2_response_parser *parser)
+{
+    h2_task *task = parser->task;
+    apr_array_header_t *hlines = parser->hlines;
+    if (hlines) {
+        apr_table_t *headers = apr_table_make(task->pool, hlines->nelts);        
+        int i;
+        
+        for (i = 0; i < hlines->nelts; ++i) {
+            char *hline = ((char **)hlines->elts)[i];
+            char *sep = ap_strchr(hline, ':');
+            if (!sep) {
+                ap_log_cerror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, task->c,
+                              APLOGNO(02955) "h2_task(%s): invalid header[%d] '%s'",
+                              task->id, i, (char*)hline);
+                /* not valid format, abort */
+                return NULL;
+            }
+            (*sep++) = '\0';
+            while (*sep == ' ' || *sep == '\t') {
+                ++sep;
+            }
+            
+            if (!h2_util_ignore_header(hline)) {
+                apr_table_merge(headers, hline, sep);
+            }
+        }
+        return headers;
+    }
+    else {
+        return apr_table_make(task->pool, 0);        
+    }
+}
+
+static apr_status_t pass_response(h2_task *task, ap_filter_t *f, 
+                                  h2_response_parser *parser) 
+{
+    apr_bucket *b;
+    apr_status_t status;
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                  "h2_from_h1(%d): output_filter called", from_h1->stream_id);
+    h2_headers *response = h2_headers_create(parser->http_status, 
+                                             make_table(parser),
+                                             NULL, task->pool);
+    apr_brigade_cleanup(parser->tmp);
+    b = h2_bucket_headers_create(task->c->bucket_alloc, response);
+    APR_BRIGADE_INSERT_TAIL(parser->tmp, b);
+    b = apr_bucket_flush_create(task->c->bucket_alloc);
+    APR_BRIGADE_INSERT_TAIL(parser->tmp, b);                      
+    status = ap_pass_brigade(f->next, parser->tmp);
+    apr_brigade_cleanup(parser->tmp);
+    
+    /* reset parser for possible next response */
+    parser->state = H2_RP_STATUS_LINE;
+    apr_array_clear(parser->hlines);
+
+    if (response->status >= 200) {
+        task->output.sent_response = 1;
+    }
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, 
+                  APLOGNO(03197) "h2_task(%s): passed response %d", 
+                  task->id, response->status);
+    return status;
+}
+
+static apr_status_t parse_status(h2_task *task, char *line)
+{
+    h2_response_parser *parser = task->output.rparser;
+    int sindex = (apr_date_checkmask(line, "HTTP/#.# ###*")? 9 : 
+                  (apr_date_checkmask(line, "HTTP/# ###*")? 7 : 0));
+    if (sindex > 0) {
+        int k = sindex + 3;
+        char keepchar = line[k];
+        line[k] = '\0';
+        parser->http_status = atoi(&line[sindex]);
+        line[k] = keepchar;
+        parser->state = H2_RP_HEADER_LINE;
+        
+        return APR_SUCCESS;
+    }
+    /* Seems like there is garbage on the connection. May be a leftover
+     * from a previous proxy request. 
+     * This should only happen if the H2_RESPONSE filter is not yet in 
+     * place (post_read_request has not been reached and the handler wants
+     * to write something. Probably just the interim response we are
+     * waiting for. But if there is other data hanging around before
+     * that, this needs to fail. */
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03467)
+                  "h2_task(%s): unable to parse status line: %s", 
+                  task->id, line);
+    return APR_EINVAL;
+}
+
+apr_status_t h2_from_h1_parse_response(h2_task *task, ap_filter_t *f, 
+                                       apr_bucket_brigade *bb)
+{
+    h2_response_parser *parser = task->output.rparser;
+    char line[HUGE_STRING_LEN];
+    apr_status_t status = APR_SUCCESS;
+
+    if (!parser) {
+        parser = apr_pcalloc(task->pool, sizeof(*parser));
+        parser->task = task;
+        parser->state = H2_RP_STATUS_LINE;
+        parser->hlines = apr_array_make(task->pool, 10, sizeof(char *));
+        task->output.rparser = parser;
+    }
     
-    if (r->header_only && from_h1->response) {
-        /* throw away any data after we have compiled the response */
-        apr_brigade_cleanup(bb);
-        return OK;
-    }
-    
-    for (b = APR_BRIGADE_FIRST(bb);
-         b != APR_BRIGADE_SENTINEL(bb);
-         b = APR_BUCKET_NEXT(b))
-    {
-        if (AP_BUCKET_IS_ERROR(b) && !eb) {
-            eb = b->data;
-            continue;
-        }
-        /*
-         * If we see an EOC bucket it is a signal that we should get out
-         * of the way doing nothing.
-         */
-        if (AP_BUCKET_IS_EOC(b)) {
-            ap_remove_output_filter(f);
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
-                          "h2_from_h1(%d): eoc bucket passed", 
-                          from_h1->stream_id);
-            return ap_pass_brigade(f->next, bb);
+    while (!APR_BRIGADE_EMPTY(bb) && status == APR_SUCCESS) {
+        switch (parser->state) {
+            case H2_RP_STATUS_LINE:
+            case H2_RP_HEADER_LINE:
+                status = get_line(parser, bb, line, sizeof(line));
+                if (status == APR_EAGAIN) {
+                    /* need more data */
+                    return APR_SUCCESS;
+                }
+                else if (status != APR_SUCCESS) {
+                    return status;
+                }
+                if (parser->state == H2_RP_STATUS_LINE) {
+                    /* instead of parsing, just take it directly */
+                    status = parse_status(task, line);
+                }
+                else if (line[0] == '\0') {
+                    /* end of headers, pass response onward */
+                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+                                  "h2_task(%s): end of response", task->id);
+                    return pass_response(task, f, parser);
+                }
+                else {
+                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+                                  "h2_task(%s): response header %s", task->id, line);
+                    status = parse_header(parser, line);
+                }
+                break;
+                
+            default:
+                return status;
         }
     }
+    return status;
+}
+
+apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+    h2_task *task = f->ctx;
+    request_rec *r = f->r;
+    apr_bucket *b, *bresp, *body_bucket = NULL, *next;
+    ap_bucket_error *eb = NULL;
+    h2_headers *response = NULL;
+    int headers_passing = 0;
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+                  "h2_task(%s): output_filter called", task->id);
     
-    if (eb) {
-        int st = eb->status;
-        apr_brigade_cleanup(bb);
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03047)
-                      "h2_from_h1(%d): err bucket status=%d", 
-                      from_h1->stream_id, st);
-        ap_die(st, r);
-        return AP_FILTER_ERROR;
-    }
-    
-    from_h1->response = create_response(from_h1, r);
-    if (from_h1->response == NULL) {
-        ap_log_cerror(APLOG_MARK, APLOG_NOTICE, 0, f->c, APLOGNO(03048)
-                      "h2_from_h1(%d): unable to create response", 
-                      from_h1->stream_id);
-        return APR_ENOMEM;
+    if (!task->output.sent_response && !f->c->aborted) {
+        /* check, if we need to send the response now. Until we actually
+         * see a DATA bucket or some EOS/EOR, we do not do so. */
+        for (b = APR_BRIGADE_FIRST(bb);
+             b != APR_BRIGADE_SENTINEL(bb);
+             b = APR_BUCKET_NEXT(b))
+        {
+            if (AP_BUCKET_IS_ERROR(b) && !eb) {
+                eb = b->data;
+            }
+            else if (AP_BUCKET_IS_EOC(b)) {
+                /* If we see an EOC bucket it is a signal that we should get out
+                 * of the way doing nothing.
+                 */
+                ap_remove_output_filter(f);
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, f->c,
+                              "h2_task(%s): eoc bucket passed", task->id);
+                return ap_pass_brigade(f->next, bb);
+            }
+            else if (H2_BUCKET_IS_HEADERS(b)) {
+                headers_passing = 1;
+            }
+            else if (!APR_BUCKET_IS_FLUSH(b)) { 
+                body_bucket = b;
+                break;
+            }
+        }
+        
+        if (eb) {
+            int st = eb->status;
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03047)
+                          "h2_task(%s): err bucket status=%d", task->id, st);
+            /* throw everything away and replace it with the error response
+             * generated by ap_die() */
+            apr_brigade_cleanup(bb);
+            ap_die(st, r);
+            return AP_FILTER_ERROR;
+        }
+        
+        if (body_bucket || !headers_passing) {
+            /* time to insert the response bucket before the body or if
+             * no h2_headers is passed, e.g. the response is empty */
+            response = create_response(task, r);
+            if (response == NULL) {
+                ap_log_cerror(APLOG_MARK, APLOG_NOTICE, 0, f->c, APLOGNO(03048)
+                              "h2_task(%s): unable to create response", task->id);
+                return APR_ENOMEM;
+            }
+            
+            bresp = h2_bucket_headers_create(f->c->bucket_alloc, response);
+            if (body_bucket) {
+                APR_BUCKET_INSERT_BEFORE(body_bucket, bresp);
+            }
+            else {
+                APR_BRIGADE_INSERT_HEAD(bb, bresp);
+            }
+            task->output.sent_response = 1;
+            r->sent_bodyct = 1;
+        }
     }
     
     if (r->header_only) {
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                      "h2_from_h1(%d): header_only, cleanup output brigade", 
-                      from_h1->stream_id);
-        apr_brigade_cleanup(bb);
-        return OK;
+                      "h2_task(%s): header_only, cleanup output brigade", 
+                      task->id);
+        b = body_bucket? body_bucket : APR_BRIGADE_FIRST(bb);
+        while (b != APR_BRIGADE_SENTINEL(bb)) {
+            next = APR_BUCKET_NEXT(b);
+            if (APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b)) {
+                break;
+            } 
+            APR_BUCKET_REMOVE(b);
+            apr_bucket_destroy(b);
+            b = next;
+        }
     }
-    
-    r->sent_bodyct = 1;         /* Whatever follows is real body stuff... */
-    
-    ap_remove_output_filter(f);
-    if (APLOGctrace1(f->c)) {
-        apr_off_t len = 0;
-        apr_brigade_length(bb, 0, &len);
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                      "h2_from_h1(%d): removed header filter, passing brigade "
-                      "len=%ld", from_h1->stream_id, (long)len);
+    else if (task->output.sent_response) {
+        /* lets get out of the way, our task is done */
+        ap_remove_output_filter(f);
     }
     return ap_pass_brigade(f->next, bb);
 }
 
-apr_status_t h2_response_trailers_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+static void make_chunk(h2_task *task, apr_bucket_brigade *bb, 
+                       apr_bucket *first, apr_off_t chunk_len, 
+                       apr_bucket *tail)
+{
+    /* Surround the buckets [first, tail[ with new buckets carrying the
+     * HTTP/1.1 chunked encoding format. If tail is NULL, the chunk extends
+     * to the end of the brigade. */
+    char buffer[128];
+    apr_bucket *c;
+    int len;
+    
+    len = apr_snprintf(buffer, H2_ALEN(buffer), 
+                       "%"APR_UINT64_T_HEX_FMT"\r\n", (apr_uint64_t)chunk_len);
+    c = apr_bucket_heap_create(buffer, len, NULL, bb->bucket_alloc);
+    APR_BUCKET_INSERT_BEFORE(first, c);
+    c = apr_bucket_heap_create("\r\n", 2, NULL, bb->bucket_alloc);
+    if (tail) {
+        APR_BUCKET_INSERT_BEFORE(tail, c);
+    }
+    else {
+        APR_BRIGADE_INSERT_TAIL(bb, c);
+    }
+    task->input.chunked_total += chunk_len;
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c,
+                  "h2_task(%s): added chunk %ld, total %ld", 
+                  task->id, (long)chunk_len, (long)task->input.chunked_total);
+}
+
+static int ser_header(void *ctx, const char *name, const char *value) 
+{
+    apr_bucket_brigade *bb = ctx;
+    apr_brigade_printf(bb, NULL, NULL, "%s: %s\r\n", name, value);
+    return 1;
+}
+
+static apr_status_t read_and_chunk(ap_filter_t *f, h2_task *task,
+                                   apr_read_type_e block) {
+    request_rec *r = f->r;
+    apr_status_t status = APR_SUCCESS;
+    apr_bucket_brigade *bb = task->input.bbchunk;
+    
+    if (!bb) {
+        bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
+        task->input.bbchunk = bb;
+    }
+    
+    if (APR_BRIGADE_EMPTY(bb)) {
+        apr_bucket *b, *next, *first_data = NULL;
+        apr_bucket_brigade *tmp;
+        apr_off_t bblen = 0;
+
+        /* get more data from the lower layer filters. Always do this
+         * in larger pieces, since we handle the read modes ourself. */
+        status = ap_get_brigade(f->next, bb, 
+                                AP_MODE_READBYTES, block, 32*1024);
+        if (status == APR_EOF) {
+            if (!task->input.eos) {
+                status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
+                task->input.eos = 1;
+                return APR_SUCCESS;
+            }
+            ap_remove_input_filter(f);
+            return status;
+            
+        }
+        else if (status != APR_SUCCESS) {
+            return status;
+        }
+
+        for (b = APR_BRIGADE_FIRST(bb); 
+             b != APR_BRIGADE_SENTINEL(bb) && !task->input.eos; 
+             b = next) {
+            next = APR_BUCKET_NEXT(b);
+            if (APR_BUCKET_IS_METADATA(b)) {
+                if (first_data) {
+                    make_chunk(task, bb, first_data, bblen, b);
+                    first_data = NULL;
+                }
+                
+                if (H2_BUCKET_IS_HEADERS(b)) {
+                    h2_headers *headers = h2_bucket_headers_get(b);
+                    
+                    ap_assert(headers);
+                    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                                  "h2_task(%s): receiving trailers", task->id);
+                    tmp = apr_brigade_split_ex(bb, b, NULL);
+                    if (!apr_is_empty_table(headers->headers)) {
+                        status = apr_brigade_puts(bb, NULL, NULL, "0\r\n");
+                        apr_table_do(ser_header, bb, headers->headers, NULL);
+                        status = apr_brigade_puts(bb, NULL, NULL, "\r\n");
+                    }
+                    else {
+                        status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
+                    }
+                    r->trailers_in = apr_table_clone(r->pool, headers->headers);
+                    APR_BUCKET_REMOVE(b);
+                    apr_bucket_destroy(b);
+                    APR_BRIGADE_CONCAT(bb, tmp);
+                    apr_brigade_destroy(tmp);
+                    task->input.eos = 1;
+                }
+                else if (APR_BUCKET_IS_EOS(b)) {
+                    tmp = apr_brigade_split_ex(bb, b, NULL);
+                    status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
+                    APR_BRIGADE_CONCAT(bb, tmp);
+                    apr_brigade_destroy(tmp);
+                    task->input.eos = 1;
+                }
+            }
+            else if (b->length == 0) {
+                APR_BUCKET_REMOVE(b);
+                apr_bucket_destroy(b);
+            } 
+            else {
+                if (!first_data) {
+                    first_data = b;
+                    bblen = 0;
+                }
+                bblen += b->length;
+            }    
+        }
+        
+        if (first_data) {
+            make_chunk(task, bb, first_data, bblen, NULL);
+        }            
+    }
+    return status;
+}
+
+apr_status_t h2_filter_request_in(ap_filter_t* f,
+                                  apr_bucket_brigade* bb,
+                                  ap_input_mode_t mode,
+                                  apr_read_type_e block,
+                                  apr_off_t readbytes)
 {
     h2_task *task = f->ctx;
-    h2_from_h1 *from_h1 = task->output.from_h1;
     request_rec *r = f->r;
-    apr_bucket *b;
+    apr_status_t status = APR_SUCCESS;
+    apr_bucket *b, *next;
+    core_server_config *conf =
+        (core_server_config *) ap_get_module_config(r->server->module_config,
+                                                    &core_module);
+
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, f->r,
+                  "h2_task(%s): request filter, exp=%d", task->id, r->expecting_100);
+    if (!task->request->chunked) {
+        status = ap_get_brigade(f->next, bb, mode, block, readbytes);
+        /* pipe data through, just take care of trailers */
+        for (b = APR_BRIGADE_FIRST(bb); 
+             b != APR_BRIGADE_SENTINEL(bb); b = next) {
+            next = APR_BUCKET_NEXT(b);
+            if (H2_BUCKET_IS_HEADERS(b)) {
+                h2_headers *headers = h2_bucket_headers_get(b);
+                ap_assert(headers);
+                ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r,
+                              "h2_task(%s): receiving trailers", task->id);
+                r->trailers_in = headers->headers;
+                if (conf && conf->merge_trailers == AP_MERGE_TRAILERS_ENABLE) {
+                    r->headers_in = apr_table_overlay(r->pool, r->headers_in,
+                                                      r->trailers_in);                    
+                }
+                APR_BUCKET_REMOVE(b);
+                apr_bucket_destroy(b);
+                ap_remove_input_filter(f);
+                break;
+            }
+        }
+        return status;
+    }
+
+    /* Things are more complicated. The standard HTTP input filter, which
+     * does a lot what we do not want to duplicate, also cares about chunked
+     * transfer encoding and trailers.
+     * We need to simulate chunked encoding for it to be happy.
+     */
+    if ((status = read_and_chunk(f, task, block)) != APR_SUCCESS) {
+        return status;
+    }
+    
+    if (mode == AP_MODE_EXHAUSTIVE) {
+        /* return all we have */
+        APR_BRIGADE_CONCAT(bb, task->input.bbchunk);
+    }
+    else if (mode == AP_MODE_READBYTES) {
+        status = h2_brigade_concat_length(bb, task->input.bbchunk, readbytes);
+    }
+    else if (mode == AP_MODE_SPECULATIVE) {
+        status = h2_brigade_copy_length(bb, task->input.bbchunk, readbytes);
+    }
+    else if (mode == AP_MODE_GETLINE) {
+        /* we are reading a single LF line, e.g. the HTTP headers. 
+         * this has the nasty side effect to split the bucket, even
+         * though it ends with CRLF and creates a 0 length bucket */
+        status = apr_brigade_split_line(bb, task->input.bbchunk, block, 
+                                        HUGE_STRING_LEN);
+        if (APLOGctrace1(f->c)) {
+            char buffer[1024];
+            apr_size_t len = sizeof(buffer)-1;
+            apr_brigade_flatten(bb, buffer, &len);
+            buffer[len] = 0;
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+                          "h2_task(%s): getline: %s",
+                          task->id, buffer);
+        }
+    }
+    else {
+        /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
+         * to support it. Seems to work. */
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
+                      APLOGNO(02942) 
+                      "h2_task, unsupported READ mode %d", mode);
+        status = APR_ENOTIMPL;
+    }
+    
+    h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, "forwarding input", bb);
+    return status;
+}
+
+apr_status_t h2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+    h2_task *task = f->ctx;
+    request_rec *r = f->r;
+    apr_bucket *b, *e;
  
-    if (from_h1 && from_h1->response) {
-        /* Detect the EOR bucket and forward any trailers that may have
-         * been set to our h2_response.
+    if (task && r) {
+        /* Detect the EOS/EOR bucket and forward any trailers that may have
+         * been set to our h2_headers.
          */
         for (b = APR_BRIGADE_FIRST(bb);
              b != APR_BRIGADE_SENTINEL(bb);
              b = APR_BUCKET_NEXT(b))
         {
-            if (AP_BUCKET_IS_EOR(b)) {
-                /* FIXME: need a better test case than this.
-                apr_table_setn(r->trailers_out, "X", "1"); */
-                if (r->trailers_out && !apr_is_empty_table(r->trailers_out)) {
-                    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03049)
-                                  "h2_from_h1(%d): trailers filter, saving trailers",
-                                  from_h1->stream_id);
-                    h2_response_set_trailers(from_h1->response,
-                                             apr_table_clone(from_h1->pool, 
-                                                             r->trailers_out));
-                }
+            if ((APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b))
+                && r->trailers_out && !apr_is_empty_table(r->trailers_out)) {
+                h2_headers *headers;
+                apr_table_t *trailers;
+                
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, f->c, APLOGNO(03049)
+                              "h2_task(%s): sending trailers", task->id);
+                trailers = apr_table_clone(r->pool, r->trailers_out);
+                headers = h2_headers_rcreate(r, HTTP_OK, trailers, r->pool);
+                e = h2_bucket_headers_create(bb->bucket_alloc, headers);
+                APR_BUCKET_INSERT_BEFORE(b, e);
+                apr_table_clear(r->trailers_out);
+                ap_remove_output_filter(f);
                 break;
             }
         }     
diff -up --new-file httpd-2.4.23/modules/http2/h2_from_h1.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_from_h1.h
--- httpd-2.4.23/modules/http2/h2_from_h1.h	2016-05-18 17:10:20.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_from_h1.h	2016-10-17 18:11:12.000000000 +0200
@@ -30,46 +30,20 @@
  * we need to have all handlers and filters involved in request/response
  * processing, so this seems to be the way for now.
  */
+struct h2_headers;
+struct h2_task;
 
-typedef enum {
-    H2_RESP_ST_STATUS_LINE, /* parsing http/1 status line */
-    H2_RESP_ST_HEADERS,     /* parsing http/1 response headers */
-    H2_RESP_ST_BODY,        /* transferring response body */
-    H2_RESP_ST_DONE         /* complete response converted */
-} h2_from_h1_state_t;
+apr_status_t h2_from_h1_parse_response(struct h2_task *task, ap_filter_t *f, 
+                                       apr_bucket_brigade *bb);
 
-struct h2_response;
+apr_status_t h2_filter_headers_out(ap_filter_t *f, apr_bucket_brigade *bb);
 
-typedef struct h2_from_h1 h2_from_h1;
+apr_status_t h2_filter_request_in(ap_filter_t* f,
+                                  apr_bucket_brigade* brigade,
+                                  ap_input_mode_t mode,
+                                  apr_read_type_e block,
+                                  apr_off_t readbytes);
 
-struct h2_from_h1 {
-    int stream_id;
-    h2_from_h1_state_t state;
-    apr_pool_t *pool;
-    apr_bucket_brigade *bb;
-    
-    apr_off_t content_length;
-    int chunked;
-    
-    int http_status;
-    apr_array_header_t *hlines;
-    
-    struct h2_response *response;
-};
-
-
-h2_from_h1 *h2_from_h1_create(int stream_id, apr_pool_t *pool);
-
-apr_status_t h2_from_h1_read_response(h2_from_h1 *from_h1,
-                                      ap_filter_t* f, apr_bucket_brigade* bb);
-
-struct h2_response *h2_from_h1_get_response(h2_from_h1 *from_h1);
-
-apr_status_t h2_response_output_filter(ap_filter_t *f, apr_bucket_brigade *bb);
-
-apr_status_t h2_response_trailers_filter(ap_filter_t *f, apr_bucket_brigade *bb);
-
-void h2_from_h1_set_basic_http_header(apr_table_t *headers, request_rec *r,
-                                      apr_pool_t *pool);
+apr_status_t h2_filter_trailers_out(ap_filter_t *f, apr_bucket_brigade *bb);
 
 #endif /* defined(__mod_h2__h2_from_h1__) */
diff -up --new-file httpd-2.4.23/modules/http2/h2.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2.h
--- httpd-2.4.23/modules/http2/h2.h	2016-05-23 12:55:29.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2.h	2017-04-18 15:12:38.000000000 +0200
@@ -47,13 +47,14 @@ extern const char *H2_MAGIC_TOKEN;
 #define H2_HEADER_PATH_LEN   5
 #define H2_CRLF             "\r\n"
 
+/* Max data size to write so it fits inside a TLS record */
+#define H2_DATA_CHUNK_SIZE          ((16*1024) - 100 - 9) 
+
 /* Maximum number of padding bytes in a frame, rfc7540 */
 #define H2_MAX_PADLEN               256
 /* Initial default window size, RFC 7540 ch. 6.5.2 */
 #define H2_INITIAL_WINDOW_SIZE      ((64*1024)-1)
 
-#define H2_HTTP_2XX(a)      ((a) >= 200 && (a) < 300)
-
 #define H2_STREAM_CLIENT_INITIATED(id)      (id&0x01)
 
 #define H2_ALEN(a)          (sizeof(a)/sizeof((a)[0]))
@@ -80,34 +81,44 @@ typedef enum {
 } h2_push_policy;
 
 typedef enum {
-    H2_STREAM_ST_IDLE,
-    H2_STREAM_ST_OPEN,
-    H2_STREAM_ST_RESV_LOCAL,
-    H2_STREAM_ST_RESV_REMOTE,
-    H2_STREAM_ST_CLOSED_INPUT,
-    H2_STREAM_ST_CLOSED_OUTPUT,
-    H2_STREAM_ST_CLOSED,
-} h2_stream_state_t;
-
-typedef enum {
     H2_SESSION_ST_INIT,             /* send initial SETTINGS, etc. */
     H2_SESSION_ST_DONE,             /* finished, connection close */
     H2_SESSION_ST_IDLE,             /* nothing to write, expecting data inc */
     H2_SESSION_ST_BUSY,             /* read/write without stop */
     H2_SESSION_ST_WAIT,             /* waiting for tasks reporting back */
-    H2_SESSION_ST_LOCAL_SHUTDOWN,   /* we announced GOAWAY */
-    H2_SESSION_ST_REMOTE_SHUTDOWN,  /* client announced GOAWAY */
+    H2_SESSION_ST_CLEANUP,          /* pool is being cleaned up */
 } h2_session_state;
 
 typedef struct h2_session_props {
-    apr_uint32_t accepted_max;      /* the highest remote stream id was/will be handled */
-    apr_uint32_t completed_max;     /* the highest remote stream completed */
-    apr_uint32_t emitted_count;     /* the number of local streams sent */
-    apr_uint32_t emitted_max;       /* the highest local stream id sent */
-    apr_uint32_t error;             /* the last session error encountered */
+    int accepted_max;      /* the highest remote stream id was/will be handled */
+    int completed_max;     /* the highest remote stream completed */
+    int emitted_count;     /* the number of local streams sent */
+    int emitted_max;       /* the highest local stream id sent */
+    int error;             /* the last session error encountered */
     unsigned int accepting : 1;     /* if the session is accepting new streams */
+    unsigned int shutdown : 1;      /* if the final GOAWAY has been sent */
 } h2_session_props;
 
+typedef enum h2_stream_state_t {
+    H2_SS_IDLE,
+    H2_SS_RSVD_R,
+    H2_SS_RSVD_L,
+    H2_SS_OPEN,
+    H2_SS_CLOSED_R,
+    H2_SS_CLOSED_L,
+    H2_SS_CLOSED,
+    H2_SS_CLEANUP,
+    H2_SS_MAX
+} h2_stream_state_t;
+
+typedef enum {
+    H2_SEV_CLOSED_L,
+    H2_SEV_CLOSED_R,
+    H2_SEV_CANCELLED,
+    H2_SEV_EOS_SENT,
+    H2_SEV_IN_DATA_PENDING,
+} h2_stream_event_t;
+
 
 /* h2_request is the transformer of HTTP2 streams into HTTP/1.1 internal
  * format that will be fed to various httpd input filters to finally
@@ -116,37 +127,23 @@ typedef struct h2_session_props {
 typedef struct h2_request h2_request;
 
 struct h2_request {
-    int id;             /* stream id */
-    int initiated_on;   /* initiating stream id (PUSH) or 0 */
-    
     const char *method; /* pseudo header values, see ch. 8.1.2.3 */
     const char *scheme;
     const char *authority;
     const char *path;
-    
     apr_table_t *headers;
-    apr_table_t *trailers;
 
     apr_time_t request_time;
-    apr_off_t content_length;
-    
-    unsigned int chunked : 1; /* iff requst body needs to be forwarded as chunked */
-    unsigned int eoh     : 1; /* iff end-of-headers has been seen and request is complete */
-    unsigned int body    : 1; /* iff this request has a body */
+    unsigned int chunked : 1;   /* iff requst body needs to be forwarded as chunked */
     unsigned int serialize : 1; /* iff this request is written in HTTP/1.1 serialization */
-    unsigned int push_policy; /* which push policy to use for this request */
 };
 
-typedef struct h2_response h2_response;
+typedef struct h2_headers h2_headers;
 
-struct h2_response {
-    int         stream_id;
-    int         rst_error;
-    int         http_status;
-    apr_off_t   content_length;
+struct h2_headers {
+    int         status;
     apr_table_t *headers;
-    apr_table_t *trailers;
-    const char  *sos_filter;
+    apr_table_t *notes;
 };
 
 typedef apr_status_t h2_io_data_cb(void *ctx, const char *data, apr_off_t len);
@@ -155,7 +152,9 @@ typedef int h2_stream_pri_cmp(int stream
 
 /* Note key to attach connection task id to conn_rec/request_rec instances */
 
-#define H2_TASK_ID_NOTE     "http2-task-id"
-
+#define H2_TASK_ID_NOTE         "http2-task-id"
+#define H2_FILTER_DEBUG_NOTE    "http2-debug"
+#define H2_HDR_CONFORMANCE      "http2-hdr-conformance"
+#define H2_HDR_CONFORMANCE_UNSAFE      "unsafe"
 
 #endif /* defined(__mod_h2__h2__) */
diff -up --new-file httpd-2.4.23/modules/http2/h2_h2.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_h2.c
--- httpd-2.4.23/modules/http2/h2_h2.c	2016-05-14 13:49:01.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_h2.c	2017-03-12 15:20:29.000000000 +0100
@@ -32,12 +32,15 @@
 #include "mod_http2.h"
 #include "h2_private.h"
 
+#include "h2_bucket_beam.h"
 #include "h2_stream.h"
 #include "h2_task.h"
 #include "h2_config.h"
 #include "h2_ctx.h"
 #include "h2_conn.h"
+#include "h2_filter.h"
 #include "h2_request.h"
+#include "h2_headers.h"
 #include "h2_session.h"
 #include "h2_util.h"
 #include "h2_h2.h"
@@ -433,6 +436,7 @@ static int cipher_is_blacklisted(const c
 static int h2_h2_process_conn(conn_rec* c);
 static int h2_h2_pre_close_conn(conn_rec* c);
 static int h2_h2_post_read_req(request_rec *r);
+static int h2_h2_late_fixups(request_rec *r);
 
 /*******************************************************************************
  * Once per lifetime init, retrieve optional functions
@@ -567,6 +571,11 @@ void h2_h2_register_hooks(void)
      * never see the response.
      */
     ap_hook_post_read_request(h2_h2_post_read_req, NULL, NULL, APR_HOOK_REALLY_FIRST);
+    ap_hook_fixups(h2_h2_late_fixups, NULL, NULL, APR_HOOK_LAST);
+
+    /* special bucket type transfer through a h2_bucket_beam */
+    h2_register_bucket_beamer(h2_bucket_headers_beam);
+    h2_register_bucket_beamer(h2_bucket_observer_beam);
 }
 
 int h2_h2_process_conn(conn_rec* c)
@@ -643,6 +652,7 @@ int h2_h2_process_conn(conn_rec* c)
             status = h2_conn_setup(ctx, c, NULL);
             ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c, "conn_setup");
             if (status != APR_SUCCESS) {
+                h2_ctx_clear(c);
                 return status;
             }
         }
@@ -665,13 +675,39 @@ static int h2_h2_pre_close_conn(conn_rec
     ctx = h2_ctx_get(c, 0);
     if (ctx) {
         /* If the session has been closed correctly already, we will not
-         * fiond a h2_ctx here. The presence indicates that the session
+         * find a h2_ctx here. The presence indicates that the session
          * is still ongoing. */
         return h2_conn_pre_close(ctx, c);
     }
     return DECLINED;
 }
 
+static void check_push(request_rec *r, const char *tag)
+{
+    const h2_config *conf = h2_config_rget(r);
+    if (!r->expecting_100 
+        && conf && conf->push_list && conf->push_list->nelts > 0) {
+        int i, old_status;
+        const char *old_line;
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, 
+                      "%s, early announcing %d resources for push",
+                      tag, conf->push_list->nelts);
+        for (i = 0; i < conf->push_list->nelts; ++i) {
+            h2_push_res *push = &APR_ARRAY_IDX(conf->push_list, i, h2_push_res);
+            apr_table_addn(r->headers_out, "Link", 
+                           apr_psprintf(r->pool, "<%s>; rel=preload%s", 
+                                        push->uri_ref, push->critical? "; critical" : ""));
+        }
+        old_status = r->status;
+        old_line = r->status_line;
+        r->status = 103;
+        r->status_line = "103 Early Hints";
+        ap_send_interim_response(r, 1);
+        r->status = old_status;
+        r->status_line = old_line;
+    }
+}
+
 static int h2_h2_post_read_req(request_rec *r)
 {
     /* slave connection? */
@@ -682,33 +718,48 @@ static int h2_h2_post_read_req(request_r
          * that we manipulate filters only once. */
         if (task && !task->filters_set) {
             ap_filter_t *f;
+            ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, 
+                          "h2_task(%s): adding request filters", task->id);
+
+            /* setup the correct filters to process the request for h2 */
+            ap_add_input_filter("H2_REQUEST", task, r, r->connection);
             
-            /* setup the correct output filters to process the response
-             * on the proper mod_http2 way. */
-            ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "adding task output filter");
-            if (task->ser_headers) {
-                ap_add_output_filter("H1_TO_H2_RESP", task, r, r->connection);
-            }
-            else {
-                /* replace the core http filter that formats response headers
-                 * in HTTP/1 with our own that collects status and headers */
-                ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
-                ap_add_output_filter("H2_RESPONSE", task, r, r->connection);
-            }
+            /* replace the core http filter that formats response headers
+             * in HTTP/1 with our own that collects status and headers */
+            ap_remove_output_filter_byhandle(r->output_filters, "HTTP_HEADER");
+            ap_add_output_filter("H2_RESPONSE", task, r, r->connection);
             
-            /* trailers processing. Incoming trailers are added to this
-             * request via our h2 input filter, outgoing trailers
-             * in a special h2 out filter. */
             for (f = r->input_filters; f; f = f->next) {
-                if (!strcmp("H2_TO_H1", f->frec->name)) {
+                if (!strcmp("H2_SLAVE_IN", f->frec->name)) {
                     f->r = r;
                     break;
                 }
             }
-            ap_add_output_filter("H2_TRAILERS", task, r, r->connection);
+            ap_add_output_filter("H2_TRAILERS_OUT", task, r, r->connection);
             task->filters_set = 1;
         }
     }
     return DECLINED;
+}
+
+static int h2_h2_late_fixups(request_rec *r)
+{
+    /* slave connection? */
+    if (r->connection->master) {
+        h2_ctx *ctx = h2_ctx_rget(r);
+        struct h2_task *task = h2_ctx_get_task(ctx);
+        if (task) {
+            /* check if we copy vs. setaside files in this location */
+            task->output.copy_files = h2_config_geti(h2_config_rget(r), 
+                                                     H2_CONF_COPY_FILES);
+            if (task->output.copy_files) {
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
+                              "h2_slave_out(%s): copy_files on", task->id);
+                h2_beam_on_file_beam(task->output.beam, h2_beam_no_files, NULL);
+            }
+            check_push(r, "late_fixup");
+        }
+    }
+    return DECLINED;
 }
 
diff -up --new-file httpd-2.4.23/modules/http2/h2_h2.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_h2.h
--- httpd-2.4.23/modules/http2/h2_h2.h	2016-03-02 12:21:28.000000000 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_h2.h	2016-08-25 14:48:18.000000000 +0200
@@ -17,7 +17,7 @@
 #define __mod_h2__h2_h2__
 
 /**
- * List of ALPN protocol identifiers that we suport in cleartext
+ * List of ALPN protocol identifiers that we support in cleartext
  * negotiations. NULL terminated.
  */
 extern const char *h2_clear_protos[];
@@ -36,7 +36,7 @@ extern const char *h2_tls_protos[];
 const char *h2_h2_err_description(unsigned int h2_error);
 
 /*
- * One time, post config intialization.
+ * One time, post config initialization.
  */
 apr_status_t h2_h2_init(apr_pool_t *pool, server_rec *s);
 
diff -up --new-file httpd-2.4.23/modules/http2/h2_headers.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_headers.c
--- httpd-2.4.23/modules/http2/h2_headers.c	1970-01-01 01:00:00.000000000 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_headers.c	2017-04-18 15:12:38.000000000 +0200
@@ -0,0 +1,177 @@
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+
+#include <apr_strings.h>
+
+#include <httpd.h>
+#include <http_core.h>
+#include <http_log.h>
+#include <util_time.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "h2_private.h"
+#include "h2_h2.h"
+#include "h2_util.h"
+#include "h2_request.h"
+#include "h2_headers.h"
+
+
+static int is_unsafe(server_rec *s) 
+{
+    core_server_config *conf = ap_get_core_module_config(s->module_config);
+    return (conf->http_conformance == AP_HTTP_CONFORMANCE_UNSAFE);
+}
+
+typedef struct {
+    apr_bucket_refcount refcount;
+    h2_headers *headers;
+} h2_bucket_headers;
+ 
+static apr_status_t bucket_read(apr_bucket *b, const char **str,
+                                apr_size_t *len, apr_read_type_e block)
+{
+    (void)b;
+    (void)block;
+    *str = NULL;
+    *len = 0;
+    return APR_SUCCESS;
+}
+
+apr_bucket * h2_bucket_headers_make(apr_bucket *b, h2_headers *r)
+{
+    h2_bucket_headers *br;
+
+    br = apr_bucket_alloc(sizeof(*br), b->list);
+    br->headers = r;
+
+    b = apr_bucket_shared_make(b, br, 0, 0);
+    b->type = &h2_bucket_type_headers;
+    
+    return b;
+} 
+
+apr_bucket * h2_bucket_headers_create(apr_bucket_alloc_t *list, 
+                                       h2_headers *r)
+{
+    apr_bucket *b = apr_bucket_alloc(sizeof(*b), list);
+
+    APR_BUCKET_INIT(b);
+    b->free = apr_bucket_free;
+    b->list = list;
+    b = h2_bucket_headers_make(b, r);
+    return b;
+}
+                                       
+h2_headers *h2_bucket_headers_get(apr_bucket *b)
+{
+    if (H2_BUCKET_IS_HEADERS(b)) {
+        return ((h2_bucket_headers *)b->data)->headers;
+    }
+    return NULL;
+}
+
+const apr_bucket_type_t h2_bucket_type_headers = {
+    "H2HEADERS", 5, APR_BUCKET_METADATA,
+    apr_bucket_destroy_noop,
+    bucket_read,
+    apr_bucket_setaside_noop,
+    apr_bucket_split_notimpl,
+    apr_bucket_shared_copy
+};
+
+apr_bucket *h2_bucket_headers_beam(struct h2_bucket_beam *beam,
+                                    apr_bucket_brigade *dest,
+                                    const apr_bucket *src)
+{
+    if (H2_BUCKET_IS_HEADERS(src)) {
+        h2_headers *r = ((h2_bucket_headers *)src->data)->headers;
+        apr_bucket *b = h2_bucket_headers_create(dest->bucket_alloc, r);
+        APR_BRIGADE_INSERT_TAIL(dest, b);
+        return b;
+    }
+    return NULL;
+}
+
+
+h2_headers *h2_headers_create(int status, apr_table_t *headers_in, 
+                                apr_table_t *notes, apr_pool_t *pool)
+{
+    h2_headers *headers = apr_pcalloc(pool, sizeof(h2_headers));
+    headers->status    = status;
+    headers->headers   = (headers_in? apr_table_copy(pool, headers_in)
+                           : apr_table_make(pool, 5));
+    headers->notes     = (notes? apr_table_copy(pool, notes)
+                           : apr_table_make(pool, 5));
+    return headers;
+}
+
+h2_headers *h2_headers_rcreate(request_rec *r, int status,
+                                 apr_table_t *header, apr_pool_t *pool)
+{
+    h2_headers *headers = h2_headers_create(status, header, r->notes, pool);
+    if (headers->status == HTTP_FORBIDDEN) {
+        const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden");
+        if (cause) {
+            /* This request triggered a TLS renegotiation that is now allowed 
+             * in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
+             */
+            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, headers->status, r,
+                          APLOGNO(03061) 
+                          "h2_headers(%ld): renegotiate forbidden, cause: %s",
+                          (long)r->connection->id, cause);
+            headers->status = H2_ERR_HTTP_1_1_REQUIRED;
+        }
+    }
+    if (is_unsafe(r->server)) {
+        apr_table_setn(headers->notes, H2_HDR_CONFORMANCE, 
+                       H2_HDR_CONFORMANCE_UNSAFE);
+    }
+    return headers;
+}
+
+h2_headers *h2_headers_copy(apr_pool_t *pool, h2_headers *h)
+{
+    return h2_headers_create(h->status, apr_table_copy(pool, h->headers), 
+                             apr_table_copy(pool, h->notes), pool);
+}
+
+h2_headers *h2_headers_die(apr_status_t type,
+                             const h2_request *req, apr_pool_t *pool)
+{
+    h2_headers *headers;
+    char *date;
+    
+    headers = apr_pcalloc(pool, sizeof(h2_headers));
+    headers->status    = (type >= 200 && type < 600)? type : 500;
+    headers->headers        = apr_table_make(pool, 5);
+    headers->notes          = apr_table_make(pool, 5);
+
+    date = apr_palloc(pool, APR_RFC822_DATE_LEN);
+    ap_recent_rfc822_date(date, req? req->request_time : apr_time_now());
+    apr_table_setn(headers->headers, "Date", date);
+    apr_table_setn(headers->headers, "Server", ap_get_server_banner());
+    
+    return headers;
+}
+
+int h2_headers_are_response(h2_headers *headers)
+{
+    return headers->status >= 200;
+}
+
diff -up --new-file httpd-2.4.23/modules/http2/h2_headers.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_headers.h
--- httpd-2.4.23/modules/http2/h2_headers.h	1970-01-01 01:00:00.000000000 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_headers.h	2017-04-18 15:12:38.000000000 +0200
@@ -0,0 +1,76 @@
+/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __mod_h2__h2_headers__
+#define __mod_h2__h2_headers__
+
+#include "h2.h"
+
+struct h2_bucket_beam;
+
+extern const apr_bucket_type_t h2_bucket_type_headers;
+
+#define H2_BUCKET_IS_HEADERS(e)     (e->type == &h2_bucket_type_headers)
+
+apr_bucket * h2_bucket_headers_make(apr_bucket *b, h2_headers *r); 
+
+apr_bucket * h2_bucket_headers_create(apr_bucket_alloc_t *list, 
+                                       h2_headers *r);
+                                       
+h2_headers *h2_bucket_headers_get(apr_bucket *b);
+
+apr_bucket *h2_bucket_headers_beam(struct h2_bucket_beam *beam,
+                                    apr_bucket_brigade *dest,
+                                    const apr_bucket *src);
+
+/**
+ * Create the headers from the given status and headers
+ * @param status the headers status
+ * @param header the headers of the headers
+ * @param notes  the notes carried by the headers
+ * @param pool the memory pool to use
+ */
+h2_headers *h2_headers_create(int status, apr_table_t *header, 
+                              apr_table_t *notes, apr_pool_t *pool);
+
+/**
+ * Create the headers from the given request_rec.
+ * @param r the request record which was processed
+ * @param status the headers status
+ * @param header the headers of the headers
+ * @param pool the memory pool to use
+ */
+h2_headers *h2_headers_rcreate(request_rec *r, int status, 
+                                 apr_table_t *header, apr_pool_t *pool);
+
+/**
+ * Clone the headers into another pool. This will not copy any
+ * header strings.
+ */
+h2_headers *h2_headers_copy(apr_pool_t *pool, h2_headers *h);
+
+/**
+ * Create the headers for the given error.
+ * @param stream_id id of the stream to create the headers for
+ * @param type the error code
+ * @param req the original h2_request
+ * @param pool the memory pool to use
+ */
+h2_headers *h2_headers_die(apr_status_t type,
+                             const struct h2_request *req, apr_pool_t *pool);
+
+int h2_headers_are_response(h2_headers *headers);
+
+#endif /* defined(__mod_h2__h2_headers__) */
diff -up --new-file httpd-2.4.23/modules/http2/h2_mplx.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_mplx.c
--- httpd-2.4.23/modules/http2/h2_mplx.c	2016-06-22 15:30:24.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_mplx.c	2017-10-13 10:37:45.000000000 +0200
@@ -17,6 +17,7 @@
 #include <stddef.h>
 #include <stdlib.h>
 
+#include <apr_atomic.h>
 #include <apr_thread_mutex.h>
 #include <apr_thread_cond.h>
 #include <apr_strings.h>
@@ -26,204 +27,115 @@
 #include <http_core.h>
 #include <http_log.h>
 
+#include <mpm_common.h>
+
 #include "mod_http2.h"
 
+#include "h2.h"
 #include "h2_private.h"
 #include "h2_bucket_beam.h"
 #include "h2_config.h"
 #include "h2_conn.h"
 #include "h2_ctx.h"
 #include "h2_h2.h"
-#include "h2_response.h"
 #include "h2_mplx.h"
 #include "h2_ngn_shed.h"
 #include "h2_request.h"
 #include "h2_stream.h"
+#include "h2_session.h"
 #include "h2_task.h"
-#include "h2_worker.h"
 #include "h2_workers.h"
 #include "h2_util.h"
 
 
-static void h2_beam_log(h2_bucket_beam *beam, int id, const char *msg, 
-                        conn_rec *c, int level)
-{
-    if (beam && APLOG_C_IS_LEVEL(c,level)) {
-        char buffer[2048];
-        apr_size_t off = 0;
-        
-        off += apr_snprintf(buffer+off, H2_ALEN(buffer)-off, "cl=%d, ", beam->closed);
-        off += h2_util_bl_print(buffer+off, H2_ALEN(buffer)-off, "red", ", ", &beam->red);
-        off += h2_util_bb_print(buffer+off, H2_ALEN(buffer)-off, "green", ", ", beam->green);
-        off += h2_util_bl_print(buffer+off, H2_ALEN(buffer)-off, "hold", ", ", &beam->hold);
-        off += h2_util_bl_print(buffer+off, H2_ALEN(buffer)-off, "purge", "", &beam->purge);
-
-        ap_log_cerror(APLOG_MARK, level, 0, c, "beam(%ld-%d): %s %s", 
-                      c->id, id, msg, buffer);
-    }
-}
-
-/* utility for iterating over ihash task sets */
+/* utility for iterating over ihash stream sets */
 typedef struct {
     h2_mplx *m;
-    h2_task *task;
+    h2_stream *stream;
     apr_time_t now;
-} task_iter_ctx;
-
-/* NULL or the mutex hold by this thread, used for recursive calls
- */
-static apr_threadkey_t *thread_lock;
+} stream_iter_ctx;
 
 apr_status_t h2_mplx_child_init(apr_pool_t *pool, server_rec *s)
 {
-    return apr_threadkey_private_create(&thread_lock, NULL, pool);
+    return APR_SUCCESS;
 }
 
-static apr_status_t enter_mutex(h2_mplx *m, int *pacquired)
-{
-    apr_status_t status;
-    void *mutex = NULL;
-    
-    /* Enter the mutex if this thread already holds the lock or
-     * if we can acquire it. Only on the later case do we unlock
-     * onleaving the mutex.
-     * This allow recursive entering of the mutex from the saem thread,
-     * which is what we need in certain situations involving callbacks
-     */
-    AP_DEBUG_ASSERT(m);
-    apr_threadkey_private_get(&mutex, thread_lock);
-    if (mutex == m->lock) {
-        *pacquired = 0;
-        return APR_SUCCESS;
-    }
+#define H2_MPLX_ENTER(m)    \
+    do { apr_status_t rv; if ((rv = apr_thread_mutex_lock(m->lock)) != APR_SUCCESS) {\
+        return rv;\
+    } } while(0)
 
-    AP_DEBUG_ASSERT(m->lock);
-    status = apr_thread_mutex_lock(m->lock);
-    *pacquired = (status == APR_SUCCESS);
-    if (*pacquired) {
-        apr_threadkey_private_set(m->lock, thread_lock);
-    }
-    return status;
-}
+#define H2_MPLX_LEAVE(m)    \
+    apr_thread_mutex_unlock(m->lock)
+ 
+#define H2_MPLX_ENTER_ALWAYS(m)    \
+    apr_thread_mutex_lock(m->lock)
 
-static void leave_mutex(h2_mplx *m, int acquired)
-{
-    if (acquired) {
-        apr_threadkey_private_set(NULL, thread_lock);
-        apr_thread_mutex_unlock(m->lock);
-    }
-}
+#define H2_MPLX_ENTER_MAYBE(m, lock)    \
+    if (lock) apr_thread_mutex_lock(m->lock)
 
-static void beam_leave(void *ctx, apr_thread_mutex_t *lock)
-{
-    leave_mutex(ctx, 1);
-}
+#define H2_MPLX_LEAVE_MAYBE(m, lock)    \
+    if (lock) apr_thread_mutex_unlock(m->lock)
 
-static apr_status_t beam_enter(void *ctx, h2_beam_lock *pbl)
-{
-    h2_mplx *m = ctx;
-    int acquired;
-    apr_status_t status;
-    
-    status = enter_mutex(m, &acquired);
-    if (status == APR_SUCCESS) {
-        pbl->mutex = m->lock;
-        pbl->leave = acquired? beam_leave : NULL;
-        pbl->leave_ctx = m;
-    }
-    return status;
-}
+static void check_data_for(h2_mplx *m, h2_stream *stream, int lock);
 
 static void stream_output_consumed(void *ctx, 
                                    h2_bucket_beam *beam, apr_off_t length)
 {
-    h2_task *task = ctx;
+    h2_stream *stream = ctx;
+    h2_task *task = stream->task;
+    
     if (length > 0 && task && task->assigned) {
         h2_req_engine_out_consumed(task->assigned, task->c, length); 
     }
 }
 
-static void stream_input_consumed(void *ctx, 
-                                  h2_bucket_beam *beam, apr_off_t length)
+static void stream_input_ev(void *ctx, h2_bucket_beam *beam)
 {
-    h2_mplx *m = ctx;
-    if (m->input_consumed && length) {
-        m->input_consumed(m->input_consumed_ctx, beam->id, length);
-    }
+    h2_stream *stream = ctx;
+    h2_mplx *m = stream->session->mplx;
+    apr_atomic_set32(&m->event_pending, 1); 
 }
 
-static int can_beam_file(void *ctx, h2_bucket_beam *beam,  apr_file_t *file)
+static void stream_input_consumed(void *ctx, h2_bucket_beam *beam, apr_off_t length)
 {
-    h2_mplx *m = ctx;
-    if (m->tx_handles_reserved > 0) {
-        --m->tx_handles_reserved;
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, m->c,
-                      "h2_mplx(%ld-%d): beaming file %s, tx_avail %d", 
-                      m->id, beam->id, beam->tag, m->tx_handles_reserved);
-        return 1;
-    }
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, m->c,
-                  "h2_mplx(%ld-%d): can_beam_file denied on %s", 
-                  m->id, beam->id, beam->tag);
-    return 0;
+    h2_stream_in_consumed(ctx, length);
 }
 
-static void have_out_data_for(h2_mplx *m, int stream_id);
-static void task_destroy(h2_mplx *m, h2_task *task, int called_from_master);
-
-static void check_tx_reservation(h2_mplx *m) 
+static void stream_joined(h2_mplx *m, h2_stream *stream)
 {
-    if (m->tx_handles_reserved <= 0) {
-        m->tx_handles_reserved += h2_workers_tx_reserve(m->workers, 
-            H2MIN(m->tx_chunk_size, h2_ihash_count(m->tasks)));
-    }
+    ap_assert(!stream->task || stream->task->worker_done);
+    
+    h2_ihash_remove(m->shold, stream->id);
+    h2_ihash_add(m->spurge, stream);
 }
 
-static void check_tx_free(h2_mplx *m) 
+static void stream_cleanup(h2_mplx *m, h2_stream *stream)
 {
-    if (m->tx_handles_reserved > m->tx_chunk_size) {
-        apr_size_t count = m->tx_handles_reserved - m->tx_chunk_size;
-        m->tx_handles_reserved = m->tx_chunk_size;
-        h2_workers_tx_free(m->workers, count);
-    }
-    else if (m->tx_handles_reserved && h2_ihash_empty(m->tasks)) {
-        h2_workers_tx_free(m->workers, m->tx_handles_reserved);
-        m->tx_handles_reserved = 0;
-    }
-}
+    ap_assert(stream->state == H2_SS_CLEANUP);
 
-static int purge_stream(void *ctx, void *val) 
-{
-    h2_mplx *m = ctx;
-    h2_stream *stream = val;
-    h2_task *task = h2_ihash_get(m->tasks, stream->id);
-    h2_ihash_remove(m->spurge, stream->id);
-    h2_stream_destroy(stream);
-    if (task) {
-        task_destroy(m, task, 1);
+    if (stream->input) {
+        h2_beam_on_consumed(stream->input, NULL, NULL, NULL);
+        h2_beam_abort(stream->input);
     }
-    return 0;
-}
-
-static void purge_streams(h2_mplx *m)
-{
-    if (!h2_ihash_empty(m->spurge)) {
-        while(!h2_ihash_iter(m->spurge, purge_stream, m)) {
-            /* repeat until empty */
-        }
-        h2_ihash_clear(m->spurge);
+    if (stream->output) {
+        h2_beam_on_produced(stream->output, NULL, NULL);
+        h2_beam_leave(stream->output);
     }
-}
+    
+    h2_stream_cleanup(stream);
 
-static void h2_mplx_destroy(h2_mplx *m)
-{
-    AP_DEBUG_ASSERT(m);
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                  "h2_mplx(%ld): destroy, tasks=%d", 
-                  m->id, (int)h2_ihash_count(m->tasks));
-    check_tx_free(m);
-    if (m->pool) {
-        apr_pool_destroy(m->pool);
+    h2_ihash_remove(m->streams, stream->id);
+    h2_iq_remove(m->q, stream->id);
+    h2_ififo_remove(m->readyq, stream->id);
+    h2_ihash_add(m->shold, stream);
+    
+    if (!stream->task || stream->task->worker_done) {
+        stream_joined(m, stream);
+    }
+    else if (stream->task) {
+        stream->task->c->aborted = 1;
+        apr_thread_cond_broadcast(m->task_thawed);
     }
 }
 
@@ -240,66 +152,83 @@ static void h2_mplx_destroy(h2_mplx *m)
  */
 h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *parent, 
                         const h2_config *conf, 
-                        apr_interval_time_t stream_timeout,
                         h2_workers *workers)
 {
     apr_status_t status = APR_SUCCESS;
-    apr_allocator_t *allocator = NULL;
+    apr_allocator_t *allocator;
+    apr_thread_mutex_t *mutex;
     h2_mplx *m;
-    AP_DEBUG_ASSERT(conf);
+    h2_ctx *ctx = h2_ctx_get(c, 0);
+    ap_assert(conf);
     
-    status = apr_allocator_create(&allocator);
-    if (status != APR_SUCCESS) {
-        return NULL;
-    }
-
     m = apr_pcalloc(parent, sizeof(h2_mplx));
     if (m) {
         m->id = c->id;
-        APR_RING_ELEM_INIT(m, link);
         m->c = c;
+        m->s = (ctx? h2_ctx_server_get(ctx) : NULL);
+        if (!m->s) {
+            m->s = c->base_server;
+        }
+        
+        /* We create a pool with its own allocator to be used for
+         * processing slave connections. This is the only way to have the
+         * processing independant of its parent pool in the sense that it
+         * can work in another thread. Also, the new allocator needs its own
+         * mutex to synchronize sub-pools.
+         */
+        status = apr_allocator_create(&allocator);
+        if (status != APR_SUCCESS) {
+            return NULL;
+        }
+        apr_allocator_max_free_set(allocator, ap_max_mem_free);
         apr_pool_create_ex(&m->pool, parent, NULL, allocator);
         if (!m->pool) {
+            apr_allocator_destroy(allocator);
             return NULL;
         }
         apr_pool_tag(m->pool, "h2_mplx");
         apr_allocator_owner_set(allocator, m->pool);
-        
+        status = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT,
+                                         m->pool);
+        if (status != APR_SUCCESS) {
+            apr_pool_destroy(m->pool);
+            return NULL;
+        }
+        apr_allocator_mutex_set(allocator, mutex);
+
         status = apr_thread_mutex_create(&m->lock, APR_THREAD_MUTEX_DEFAULT,
                                          m->pool);
         if (status != APR_SUCCESS) {
-            h2_mplx_destroy(m);
+            apr_pool_destroy(m->pool);
             return NULL;
         }
         
         status = apr_thread_cond_create(&m->task_thawed, m->pool);
         if (status != APR_SUCCESS) {
-            h2_mplx_destroy(m);
+            apr_pool_destroy(m->pool);
             return NULL;
         }
     
-        m->bucket_alloc = apr_bucket_alloc_create(m->pool);
         m->max_streams = h2_config_geti(conf, H2_CONF_MAX_STREAMS);
         m->stream_max_mem = h2_config_geti(conf, H2_CONF_STREAM_MAX_MEM);
 
         m->streams = h2_ihash_create(m->pool, offsetof(h2_stream,id));
+        m->sredo = h2_ihash_create(m->pool, offsetof(h2_stream,id));
         m->shold = h2_ihash_create(m->pool, offsetof(h2_stream,id));
         m->spurge = h2_ihash_create(m->pool, offsetof(h2_stream,id));
         m->q = h2_iq_create(m->pool, m->max_streams);
-        m->sready = h2_ihash_create(m->pool, offsetof(h2_stream,id));
-        m->sresume = h2_ihash_create(m->pool, offsetof(h2_stream,id));
-        m->tasks = h2_ihash_create(m->pool, offsetof(h2_task,stream_id));
 
-        m->stream_timeout = stream_timeout;
+        status = h2_ififo_set_create(&m->readyq, m->pool, m->max_streams);
+        if (status != APR_SUCCESS) {
+            apr_pool_destroy(m->pool);
+            return NULL;
+        }
+
         m->workers = workers;
-        m->workers_max = workers->max_workers;
-        m->workers_def_limit = 4;
-        m->workers_limit = m->workers_def_limit;
+        m->max_active = workers->max_workers;
+        m->limit_active = 6; /* the original h1 max parallel connections */
         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;
+        m->limit_change_interval = apr_time_from_msec(100);
         
         m->spare_slaves = apr_array_make(m->pool, 10, sizeof(conn_rec*));
         
@@ -310,411 +239,330 @@ h2_mplx *h2_mplx_create(conn_rec *c, apr
     return m;
 }
 
-apr_uint32_t h2_mplx_shutdown(h2_mplx *m)
+int h2_mplx_shutdown(h2_mplx *m)
 {
-    int acquired, max_stream_started = 0;
+    int max_stream_started = 0;
     
-    if (enter_mutex(m, &acquired) == APR_SUCCESS) {
-        max_stream_started = m->max_stream_started;
-        /* Clear schedule queue, disabling existing streams from starting */ 
-        h2_iq_clear(m->q);
-        leave_mutex(m, acquired);
-    }
+    H2_MPLX_ENTER(m);
+
+    max_stream_started = m->max_stream_started;
+    /* Clear schedule queue, disabling existing streams from starting */ 
+    h2_iq_clear(m->q);
+
+    H2_MPLX_LEAVE(m);
     return max_stream_started;
 }
 
-static void input_consumed_signal(h2_mplx *m, h2_stream *stream)
+static int input_consumed_signal(h2_mplx *m, h2_stream *stream)
 {
-    if (stream->input && stream->started) {
-        h2_beam_send(stream->input, NULL, 0); /* trigger updates */
+    if (stream->input) {
+        return h2_beam_report_consumption(stream->input);
     }
+    return 0;
+}
+
+static int report_consumption_iter(void *ctx, void *val)
+{
+    h2_stream *stream = val;
+    h2_mplx *m = ctx;
+    
+    input_consumed_signal(m, stream);
+    if (stream->state == H2_SS_CLOSED_L
+        && (!stream->task || stream->task->worker_done)) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, 
+                      H2_STRM_LOG(APLOGNO(10026), stream, "remote close missing")); 
+        nghttp2_submit_rst_stream(stream->session->ngh2, NGHTTP2_FLAG_NONE, 
+                                  stream->id, NGHTTP2_NO_ERROR);
+    }
+    return 1;
 }
 
 static int output_consumed_signal(h2_mplx *m, h2_task *task)
 {
-    if (task->output.beam && task->worker_started && task->assigned) {
-        /* trigger updates */
-        h2_beam_send(task->output.beam, NULL, APR_NONBLOCK_READ);
+    if (task->output.beam) {
+        return h2_beam_report_consumption(task->output.beam);
     }
     return 0;
 }
 
-
-static void task_destroy(h2_mplx *m, h2_task *task, int called_from_master)
+static void task_destroy(h2_mplx *m, h2_task *task)
 {
     conn_rec *slave = NULL;
     int reuse_slave = 0;
-    apr_status_t status;
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, m->c, 
-                  "h2_task(%s): destroy", task->id);
-    if (called_from_master) {
-        /* Process outstanding events before destruction */
-        h2_stream *stream = h2_ihash_get(m->streams, task->stream_id);
-        if (stream) {
-            input_consumed_signal(m, stream);
-        }
-    }
-    
-    /* The pool is cleared/destroyed which also closes all
-     * allocated file handles. Give this count back to our
-     * file handle pool. */
-    if (task->output.beam) {
-        m->tx_handles_reserved += 
-        h2_beam_get_files_beamed(task->output.beam);
-        h2_beam_on_produced(task->output.beam, NULL, NULL);
-        status = h2_beam_shutdown(task->output.beam, APR_NONBLOCK_READ, 1);
-        if (status != APR_SUCCESS){
-            ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, m->c, 
-                          APLOGNO(03385) "h2_task(%s): output shutdown "
-                          "incomplete", task->id);
-        }
-    }
     
     slave = task->c;
-    reuse_slave = ((m->spare_slaves->nelts < m->spare_slaves->nalloc)
-                   && !task->rst_error);
-    
-    h2_ihash_remove(m->tasks, task->stream_id);
-    if (m->redo_tasks) {
-        h2_ihash_remove(m->redo_tasks, task->stream_id);
-    }
-    h2_task_destroy(task);
 
+    if (m->s->keep_alive_max == 0 || slave->keepalives < m->s->keep_alive_max) {
+        reuse_slave = ((m->spare_slaves->nelts < (m->limit_active * 3 / 2))
+                       && !task->rst_error);
+    }
+    
     if (slave) {
         if (reuse_slave && slave->keepalive == AP_CONN_KEEPALIVE) {
+            h2_beam_log(task->output.beam, m->c, APLOG_DEBUG, 
+                        APLOGNO(03385) "h2_task_destroy, reuse slave");    
+            h2_task_destroy(task);
             APR_ARRAY_PUSH(m->spare_slaves, conn_rec*) = slave;
         }
         else {
+            h2_beam_log(task->output.beam, m->c, APLOG_TRACE1, 
+                        "h2_task_destroy, destroy slave");    
             slave->sbh = NULL;
-            h2_slave_destroy(slave, NULL);
+            h2_slave_destroy(slave);
         }
     }
-    
-    check_tx_free(m);
 }
 
-static void stream_done(h2_mplx *m, h2_stream *stream, int rst_error) 
-{
-    h2_task *task;
+static int stream_destroy_iter(void *ctx, void *val) 
+{   
+    h2_mplx *m = ctx;
+    h2_stream *stream = val;
+
+    h2_ihash_remove(m->spurge, stream->id);
+    ap_assert(stream->state == H2_SS_CLEANUP);
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, m->c, 
-                  "h2_stream(%ld-%d): done", m->c->id, stream->id);
-    /* Situation: we are, on the master connection, done with processing
-     * the stream. Either we have handled it successfully, or the stream
-     * was reset by the client or the connection is gone and we are 
-     * shutting down the whole session.
-     *
-     * We possibly have created a task for this stream to be processed
-     * on a slave connection. The processing might actually be ongoing
-     * right now or has already finished. A finished task waits for its
-     * stream to be done. This is the common case.
-     * 
-     * If the stream had input (e.g. the request had a body), a task
-     * may have read, or is still reading buckets from the input beam.
-     * This means that the task is referencing memory from the stream's
-     * pool (or the master connection bucket alloc). Before we can free
-     * the stream pool, we need to make sure that those references are
-     * gone. This is what h2_beam_shutdown() on the input waits for.
-     *
-     * With the input handled, we can tear down that beam and care
-     * about the output beam. The stream might still have buffered some
-     * buckets read from the output, so we need to get rid of those. That
-     * is done by h2_stream_cleanup().
-     *
-     * Now it is save to destroy the task (if it exists and is finished).
-     * 
-     * FIXME: we currently destroy the stream, even if the task is still
-     * ongoing. This is not ok, since task->request is coming from stream
-     * memory. We should either copy it on task creation or wait with the
-     * stream destruction until the task is done. 
-     */
-    h2_iq_remove(m->q, stream->id);
-    h2_ihash_remove(m->sready, stream->id);
-    h2_ihash_remove(m->sresume, stream->id);
-    h2_ihash_remove(m->streams, stream->id);
     if (stream->input) {
-        m->tx_handles_reserved += h2_beam_get_files_beamed(stream->input);
-        h2_beam_on_consumed(stream->input, NULL, NULL);
-        /* Let anyone blocked reading know that there is no more to come */
-        h2_beam_abort(stream->input);
-        /* Remove mutex after, so that abort still finds cond to signal */
-        h2_beam_mutex_set(stream->input, NULL, NULL, NULL);
+        /* Process outstanding events before destruction */
+        input_consumed_signal(m, stream);    
+        h2_beam_log(stream->input, m->c, APLOG_TRACE2, "stream_destroy");
+        h2_beam_destroy(stream->input);
+        stream->input = NULL;
     }
-    h2_stream_cleanup(stream);
 
-    task = h2_ihash_get(m->tasks, stream->id);
-    if (task) {
-        if (!task->worker_done) {
-            /* task still running, cleanup once it is done */
-            if (rst_error) {
-                h2_task_rst(task, rst_error);
-            }
-            h2_ihash_add(m->shold, stream);
-            return;
-        }
-        else {
-            /* already finished */
-            task_destroy(m, task, 0);
-        }
+    if (stream->task) {
+        task_destroy(m, stream->task);
+        stream->task = NULL;
     }
     h2_stream_destroy(stream);
+    return 0;
 }
 
-static int stream_done_iter(void *ctx, void *val)
+static void purge_streams(h2_mplx *m, int lock)
 {
-    stream_done((h2_mplx*)ctx, val, 0);
-    return 0;
+    if (!h2_ihash_empty(m->spurge)) {
+        H2_MPLX_ENTER_MAYBE(m, lock);
+        while (!h2_ihash_iter(m->spurge, stream_destroy_iter, m)) {
+            /* repeat until empty */
+        }
+        H2_MPLX_LEAVE_MAYBE(m, lock);
+    }
 }
 
-static int task_print(void *ctx, void *val)
+typedef struct {
+    h2_mplx_stream_cb *cb;
+    void *ctx;
+} stream_iter_ctx_t;
+
+static int stream_iter_wrap(void *ctx, void *stream)
 {
-    h2_mplx *m = ctx;
-    h2_task *task = val;
+    stream_iter_ctx_t *x = ctx;
+    return x->cb(stream, x->ctx);
+}
+
+apr_status_t h2_mplx_stream_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx)
+{
+    stream_iter_ctx_t x;
+    
+    H2_MPLX_ENTER(m);
 
-    if (task && task->request) {
-        h2_stream *stream = h2_ihash_get(m->streams, task->stream_id);
+    x.cb = cb;
+    x.ctx = ctx;
+    h2_ihash_iter(m->streams, stream_iter_wrap, &x);
+        
+    H2_MPLX_LEAVE(m);
+    return APR_SUCCESS;
+}
 
-        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, /* NO APLOGNO */
-                      "->03198: h2_stream(%s): %s %s %s -> %s %d"
-                      "[orph=%d/started=%d/done=%d]", 
-                      task->id, task->request->method, 
-                      task->request->authority, task->request->path,
-                      task->response? "http" : (task->rst_error? "reset" : "?"),
-                      task->response? task->response->http_status : task->rst_error,
-                      (stream? 0 : 1), task->worker_started, 
-                      task->worker_done);
-    }
-    else if (task) {
-        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, /* NO APLOGNO */
-                      "->03198: h2_stream(%ld-%d): NULL", m->id, task->stream_id);
+static int report_stream_iter(void *ctx, void *val) {
+    h2_mplx *m = ctx;
+    h2_stream *stream = val;
+    h2_task *task = stream->task;
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                  H2_STRM_MSG(stream, "started=%d, scheduled=%d, ready=%d, "
+                              "out_buffer=%ld"), 
+                  !!stream->task, stream->scheduled, h2_stream_is_ready(stream),
+                  (long)h2_beam_get_buffered(stream->output));
+    if (task) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, /* NO APLOGNO */
+                      H2_STRM_MSG(stream, "->03198: %s %s %s"
+                      "[started=%d/done=%d/frozen=%d]"), 
+                      task->request->method, task->request->authority, 
+                      task->request->path, task->worker_started, 
+                      task->worker_done, task->frozen);
     }
     else {
-        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, /* NO APLOGNO */
-                      "->03198: h2_stream(%ld-NULL): NULL", m->id);
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, /* NO APLOGNO */
+                      H2_STRM_MSG(stream, "->03198: no task"));
     }
     return 1;
 }
 
-static int task_abort_connection(void *ctx, void *val)
-{
-    h2_task *task = val;
-    if (task->c) {
-        task->c->aborted = 1;
-    }
-    if (task->input.beam) {
-        h2_beam_abort(task->input.beam);
-    }
-    if (task->output.beam) {
-        h2_beam_abort(task->output.beam);
-    }
+static int unexpected_stream_iter(void *ctx, void *val) {
+    h2_mplx *m = ctx;
+    h2_stream *stream = val;
+    ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, /* NO APLOGNO */
+                  H2_STRM_MSG(stream, "unexpected, started=%d, scheduled=%d, ready=%d"), 
+                  !!stream->task, stream->scheduled, h2_stream_is_ready(stream));
     return 1;
 }
 
-static int report_stream_iter(void *ctx, void *val) {
+static int stream_cancel_iter(void *ctx, void *val) {
     h2_mplx *m = ctx;
     h2_stream *stream = val;
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                  "h2_mplx(%ld-%d): exists, started=%d, scheduled=%d, "
-                  "submitted=%d, suspended=%d", 
-                  m->id, stream->id, stream->started, stream->scheduled,
-                  stream->submitted, stream->suspended);
-    return 1;
+
+    /* disabled input consumed reporting */
+    if (stream->input) {
+        h2_beam_on_consumed(stream->input, NULL, NULL, NULL);
+    }
+    /* take over event monitoring */
+    h2_stream_set_monitor(stream, NULL);
+    /* Reset, should transit to CLOSED state */
+    h2_stream_rst(stream, H2_ERR_NO_ERROR);
+    /* All connection data has been sent, simulate cleanup */
+    h2_stream_dispatch(stream, H2_SEV_EOS_SENT);
+    stream_cleanup(m, stream);  
+    return 0;
 }
 
-apr_status_t h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
+void h2_mplx_release_and_join(h2_mplx *m, apr_thread_cond_t *wait)
 {
     apr_status_t status;
-    int acquired;
+    int i, wait_secs = 60;
 
+    /* How to shut down a h2 connection:
+     * 0. abort and tell the workers that no more tasks will come from us */
+    m->aborted = 1;
     h2_workers_unregister(m->workers, m);
     
-    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
-        int i, wait_secs = 5;
+    H2_MPLX_ENTER_ALWAYS(m);
 
-        if (!h2_ihash_empty(m->streams) && APLOGctrace1(m->c)) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                          "h2_mplx(%ld): release_join with %d streams open, "
-                          "%d streams resume, %d streams ready, %d tasks", 
-                          m->id, (int)h2_ihash_count(m->streams),
-                          (int)h2_ihash_count(m->sresume), 
-                          (int)h2_ihash_count(m->sready), 
-                          (int)h2_ihash_count(m->tasks));
-            h2_ihash_iter(m->streams, report_stream_iter, m);
-        }
-        
-        /* disable WINDOW_UPDATE callbacks */
-        h2_mplx_set_consumed_cb(m, NULL, NULL);
-        
-        if (!h2_ihash_empty(m->shold)) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                          "h2_mplx(%ld): start release_join with %d streams in hold", 
-                          m->id, (int)h2_ihash_count(m->shold));
-        }
-        if (!h2_ihash_empty(m->spurge)) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                          "h2_mplx(%ld): start release_join with %d streams to purge", 
-                          m->id, (int)h2_ihash_count(m->spurge));
-        }
-        
-        h2_iq_clear(m->q);
-        apr_thread_cond_broadcast(m->task_thawed);
-        while (!h2_ihash_iter(m->streams, stream_done_iter, m)) {
-            /* iterate until all streams have been removed */
-        }
-        AP_DEBUG_ASSERT(h2_ihash_empty(m->streams));
-    
-        if (!h2_ihash_empty(m->shold)) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                          "h2_mplx(%ld): 2. release_join with %d streams in hold", 
-                          m->id, (int)h2_ihash_count(m->shold));
-        }
-        if (!h2_ihash_empty(m->spurge)) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                          "h2_mplx(%ld): 2. release_join with %d streams to purge", 
-                          m->id, (int)h2_ihash_count(m->spurge));
-        }
-        
-        /* If we still have busy workers, we cannot release our memory
-         * pool yet, as tasks have references to us.
-         * Any operation on the task slave connection will from now on
-         * be errored ECONNRESET/ABORTED, so processing them should fail 
-         * and workers *should* return in a timely fashion.
-         */
-        for (i = 0; m->workers_busy > 0; ++i) {
-            h2_ihash_iter(m->tasks, task_abort_connection, m);
-            
-            m->join_wait = wait;
-            status = apr_thread_cond_timedwait(wait, m->lock, apr_time_from_sec(wait_secs));
-            
-            if (APR_STATUS_IS_TIMEUP(status)) {
-                if (i > 0) {
-                    /* Oh, oh. Still we wait for assigned  workers to report that 
-                     * they are done. Unless we have a bug, a worker seems to be hanging. 
-                     * If we exit now, all will be deallocated and the worker, once 
-                     * it does return, will walk all over freed memory...
-                     */
-                    ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, APLOGNO(03198)
-                                  "h2_mplx(%ld): release, waiting for %d seconds now for "
-                                  "%d h2_workers to return, have still %d tasks outstanding", 
-                                  m->id, i*wait_secs, m->workers_busy,
-                                  (int)h2_ihash_count(m->tasks));
-                    if (i == 1) {
-                        h2_ihash_iter(m->tasks, task_print, m);
-                    }
-                }
-                h2_mplx_abort(m);
-                apr_thread_cond_broadcast(m->task_thawed);
-            }
-        }
-        
-        AP_DEBUG_ASSERT(h2_ihash_empty(m->shold));
-        if (!h2_ihash_empty(m->spurge)) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                          "h2_mplx(%ld): 3. release_join %d streams to purge", 
-                          m->id, (int)h2_ihash_count(m->spurge));
-            purge_streams(m);
-        }
-        AP_DEBUG_ASSERT(h2_ihash_empty(m->spurge));
-        
-        if (!h2_ihash_empty(m->tasks)) {
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, APLOGNO(03056)
-                          "h2_mplx(%ld): release_join -> destroy, "
-                          "%d tasks still present", 
-                          m->id, (int)h2_ihash_count(m->tasks));
-        }
-        leave_mutex(m, acquired);
-        h2_mplx_destroy(m);
-        /* all gone */
+    /* How to shut down a h2 connection:
+     * 1. cancel all streams still active */
+    while (!h2_ihash_iter(m->streams, stream_cancel_iter, m)) {
+        /* until empty */
+    }
+    
+    /* 2. terminate ngn_shed, no more streams
+     * should be scheduled or in the active set */
+    h2_ngn_shed_abort(m->ngn_shed);
+    ap_assert(h2_ihash_empty(m->streams));
+    ap_assert(h2_iq_empty(m->q));
+    
+    /* 3. while workers are busy on this connection, meaning they
+     *    are processing tasks from this connection, wait on them finishing
+     *    in order to wake us and let us check again. 
+     *    Eventually, this has to succeed. */    
+    m->join_wait = wait;
+    for (i = 0; h2_ihash_count(m->shold) > 0; ++i) {        
+        status = apr_thread_cond_timedwait(wait, m->lock, apr_time_from_sec(wait_secs));
+        
+        if (APR_STATUS_IS_TIMEUP(status)) {
+            /* This can happen if we have very long running requests
+             * that do not time out on IO. */
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, APLOGNO(03198)
+                          "h2_mplx(%ld): waited %d sec for %d tasks", 
+                          m->id, i*wait_secs, (int)h2_ihash_count(m->shold));
+            h2_ihash_iter(m->shold, report_stream_iter, m);
+        }
+    }
+    m->join_wait = NULL;
+    
+    /* 4. close the h2_req_enginge shed */
+    h2_ngn_shed_destroy(m->ngn_shed);
+    m->ngn_shed = NULL;
+    
+    /* 4. With all workers done, all streams should be in spurge */
+    if (!h2_ihash_empty(m->shold)) {
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, APLOGNO(03516)
+                      "h2_mplx(%ld): unexpected %d streams in hold", 
+                      m->id, (int)h2_ihash_count(m->shold));
+        h2_ihash_iter(m->shold, unexpected_stream_iter, m);
     }
-    return status;
+    
+    H2_MPLX_LEAVE(m);
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                  "h2_mplx(%ld): released", m->id);
 }
 
-void h2_mplx_abort(h2_mplx *m)
+apr_status_t h2_mplx_stream_cleanup(h2_mplx *m, h2_stream *stream)
 {
-    int acquired;
+    H2_MPLX_ENTER(m);
     
-    AP_DEBUG_ASSERT(m);
-    if (!m->aborted && enter_mutex(m, &acquired) == APR_SUCCESS) {
-        m->aborted = 1;
-        h2_ngn_shed_abort(m->ngn_shed);
-        leave_mutex(m, acquired);
-    }
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c, 
+                  H2_STRM_MSG(stream, "cleanup"));
+    stream_cleanup(m, stream);        
+    
+    H2_MPLX_LEAVE(m);
+    return APR_SUCCESS;
 }
 
-apr_status_t h2_mplx_stream_done(h2_mplx *m, h2_stream *stream)
+h2_stream *h2_mplx_stream_get(h2_mplx *m, int id)
 {
-    apr_status_t status = APR_SUCCESS;
-    int acquired;
+    h2_stream *s = NULL;
     
-    AP_DEBUG_ASSERT(m);
-    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c, 
-                      "h2_mplx(%ld-%d): marking stream as done.", 
-                      m->id, stream->id);
-        stream_done(m, stream, stream->rst_error);
-        purge_streams(m);
-        leave_mutex(m, acquired);
-    }
-    return status;
+    H2_MPLX_ENTER_ALWAYS(m);
+
+    s = h2_ihash_get(m->streams, id);
+
+    H2_MPLX_LEAVE(m);
+    return s;
 }
 
-void h2_mplx_set_consumed_cb(h2_mplx *m, h2_mplx_consumed_cb *cb, void *ctx)
+static void output_produced(void *ctx, h2_bucket_beam *beam, apr_off_t bytes)
 {
-    m->input_consumed = cb;
-    m->input_consumed_ctx = ctx;
+    h2_stream *stream = ctx;
+    h2_mplx *m = stream->session->mplx;
+    
+    check_data_for(m, stream, 1);
 }
 
-static apr_status_t out_open(h2_mplx *m, int stream_id, h2_response *response)
+static apr_status_t out_open(h2_mplx *m, int stream_id, h2_bucket_beam *beam)
 {
     apr_status_t status = APR_SUCCESS;
-    h2_task *task = h2_ihash_get(m->tasks, stream_id);
     h2_stream *stream = h2_ihash_get(m->streams, stream_id);
     
-    if (!task || !stream) {
+    if (!stream || !stream->task || m->aborted) {
         return APR_ECONNABORTED;
     }
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                  "h2_mplx(%s): open response: %d, rst=%d",
-                  task->id, response->http_status, response->rst_error);
-    
-    h2_task_set_response(task, response);
+    ap_assert(stream->output == NULL);
+    stream->output = beam;
     
-    if (task->output.beam) {
-        h2_beam_buffer_size_set(task->output.beam, m->stream_max_mem);
-        h2_beam_timeout_set(task->output.beam, m->stream_timeout);
-        h2_beam_on_consumed(task->output.beam, stream_output_consumed, task);
-        m->tx_handles_reserved -= h2_beam_get_files_beamed(task->output.beam);
-        h2_beam_on_file_beam(task->output.beam, can_beam_file, m);
-        h2_beam_mutex_set(task->output.beam, beam_enter, task->cond, m);
+    if (APLOGctrace2(m->c)) {
+        h2_beam_log(beam, m->c, APLOG_TRACE2, "out_open");
+    }
+    else {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, m->c,
+                      "h2_mplx(%s): out open", stream->task->id);
     }
     
-    h2_ihash_add(m->sready, stream);
-    if (response && response->http_status < 300) {
-        /* we might see some file buckets in the output, see
-         * if we have enough handles reserved. */
-        check_tx_reservation(m);
+    h2_beam_on_consumed(stream->output, NULL, stream_output_consumed, stream);
+    h2_beam_on_produced(stream->output, output_produced, stream);
+    if (stream->task->output.copy_files) {
+        h2_beam_on_file_beam(stream->output, h2_beam_no_files, NULL);
     }
-    have_out_data_for(m, stream_id);
+    
+    /* we might see some file buckets in the output, see
+     * if we have enough handles reserved. */
+    check_data_for(m, stream, 0);
     return status;
 }
 
-apr_status_t h2_mplx_out_open(h2_mplx *m, int stream_id, h2_response *response)
+apr_status_t h2_mplx_out_open(h2_mplx *m, int stream_id, h2_bucket_beam *beam)
 {
     apr_status_t status;
-    int acquired;
     
-    AP_DEBUG_ASSERT(m);
-    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
-        if (m->aborted) {
-            status = APR_ECONNABORTED;
-        }
-        else {
-            status = out_open(m, stream_id, response);
-        }
-        leave_mutex(m, acquired);
+    H2_MPLX_ENTER(m);
+
+    if (m->aborted) {
+        status = APR_ECONNABORTED;
     }
+    else {
+        status = out_open(m, stream_id, beam);
+    }
+
+    H2_MPLX_LEAVE(m);
     return status;
 }
 
@@ -726,31 +574,21 @@ static apr_status_t out_close(h2_mplx *m
     if (!task) {
         return APR_ECONNABORTED;
     }
-
+    if (task->c) {
+        ++task->c->keepalives;
+    }
+    
     stream = h2_ihash_get(m->streams, task->stream_id);
     if (!stream) {
         return APR_ECONNABORTED;
     }
 
-    if (!task->response && !task->rst_error) {
-        /* In case a close comes before a response was created,
-         * insert an error one so that our streams can properly reset.
-         */
-        h2_response *r = h2_response_die(task->stream_id, 500, 
-                                         task->request, m->pool);
-        status = out_open(m, task->stream_id, r);
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, m->c, APLOGNO(03393)
-                      "h2_mplx(%s): close, no response, no rst", task->id);
-    }
     ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, m->c,
                   "h2_mplx(%s): close", task->id);
-    if (task->output.beam) {
-        status = h2_beam_close(task->output.beam);
-        h2_beam_log(task->output.beam, task->stream_id, "out_close", m->c, 
-                    APLOG_TRACE2);
-    }
+    status = h2_beam_close(task->output.beam);
+    h2_beam_log(task->output.beam, m->c, APLOG_TRACE2, "out_close");
     output_consumed_signal(m, task);
-    have_out_data_for(m, task->stream_id);
+    check_data_for(m, stream, 0);
     return status;
 }
 
@@ -758,313 +596,305 @@ apr_status_t h2_mplx_out_trywait(h2_mplx
                                  apr_thread_cond_t *iowait)
 {
     apr_status_t status;
-    int acquired;
     
-    AP_DEBUG_ASSERT(m);
-    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
-        if (m->aborted) {
-            status = APR_ECONNABORTED;
-        }
-        else if (!h2_ihash_empty(m->sready) || !h2_ihash_empty(m->sresume)) {
-            status = APR_SUCCESS;
-        }
-        else {
-            purge_streams(m);
-            m->added_output = iowait;
-            status = apr_thread_cond_timedwait(m->added_output, m->lock, timeout);
-            if (APLOGctrace2(m->c)) {
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                              "h2_mplx(%ld): trywait on data for %f ms)",
-                              m->id, timeout/1000.0);
-            }
-            m->added_output = NULL;
+    H2_MPLX_ENTER(m);
+
+    if (m->aborted) {
+        status = APR_ECONNABORTED;
+    }
+    else if (h2_mplx_has_master_events(m)) {
+        status = APR_SUCCESS;
+    }
+    else {
+        purge_streams(m, 0);
+        h2_ihash_iter(m->streams, report_consumption_iter, m);
+        m->added_output = iowait;
+        status = apr_thread_cond_timedwait(m->added_output, m->lock, timeout);
+        if (APLOGctrace2(m->c)) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
+                          "h2_mplx(%ld): trywait on data for %f ms)",
+                          m->id, timeout/1000.0);
         }
-        leave_mutex(m, acquired);
+        m->added_output = NULL;
     }
+
+    H2_MPLX_LEAVE(m);
     return status;
 }
 
-static void have_out_data_for(h2_mplx *m, int stream_id)
+static void check_data_for(h2_mplx *m, h2_stream *stream, int lock)
 {
-    (void)stream_id;
-    AP_DEBUG_ASSERT(m);
-    if (m->added_output) {
-        apr_thread_cond_signal(m->added_output);
+    if (h2_ififo_push(m->readyq, stream->id) == APR_SUCCESS) {
+        apr_atomic_set32(&m->event_pending, 1);
+        H2_MPLX_ENTER_MAYBE(m, lock);
+        if (m->added_output) {
+            apr_thread_cond_signal(m->added_output);
+        }
+        H2_MPLX_LEAVE_MAYBE(m, lock);
     }
 }
 
 apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx)
 {
     apr_status_t status;
-    int acquired;
     
-    AP_DEBUG_ASSERT(m);
-    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
-        if (m->aborted) {
-            status = APR_ECONNABORTED;
+    H2_MPLX_ENTER(m);
+
+    if (m->aborted) {
+        status = APR_ECONNABORTED;
+    }
+    else {
+        h2_iq_sort(m->q, cmp, ctx);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                      "h2_mplx(%ld): reprioritize tasks", m->id);
+        status = APR_SUCCESS;
+    }
+
+    H2_MPLX_LEAVE(m);
+    return status;
+}
+
+static void register_if_needed(h2_mplx *m) 
+{
+    if (!m->aborted && !m->is_registered && !h2_iq_empty(m->q)) {
+        apr_status_t status = h2_workers_register(m->workers, m); 
+        if (status == APR_SUCCESS) {
+            m->is_registered = 1;
         }
         else {
-            h2_iq_sort(m->q, cmp, ctx);
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                          "h2_mplx(%ld): reprioritize tasks", m->id);
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, status, m->c, APLOGNO(10021)
+                          "h2_mplx(%ld): register at workers", m->id);
         }
-        leave_mutex(m, acquired);
     }
-    return status;
 }
 
 apr_status_t h2_mplx_process(h2_mplx *m, struct h2_stream *stream, 
                              h2_stream_pri_cmp *cmp, void *ctx)
 {
     apr_status_t status;
-    int do_registration = 0;
-    int acquired;
     
-    AP_DEBUG_ASSERT(m);
-    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
-        if (m->aborted) {
-            status = APR_ECONNABORTED;
+    H2_MPLX_ENTER(m);
+
+    if (m->aborted) {
+        status = APR_ECONNABORTED;
+    }
+    else {
+        status = APR_SUCCESS;
+        h2_ihash_add(m->streams, stream);
+        if (h2_stream_is_ready(stream)) {
+            /* already have a response */
+            check_data_for(m, stream, 0);
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                          H2_STRM_MSG(stream, "process, add to readyq")); 
         }
         else {
-            h2_ihash_add(m->streams, stream);
-            if (stream->response) {
-                /* already have a respone, schedule for submit */
-                h2_ihash_add(m->sready, stream);
-            }
-            else {
-                h2_beam_create(&stream->input, stream->pool, stream->id, 
-                               "input", 0);
-                if (!m->need_registration) {
-                    m->need_registration = h2_iq_empty(m->q);
-                }
-                if (m->workers_busy < m->workers_max) {
-                    do_registration = m->need_registration;
-                }
-                h2_iq_add(m->q, stream->id, cmp, ctx);
-                
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, m->c,
-                              "h2_mplx(%ld-%d): process, body=%d", 
-                              m->c->id, stream->id, stream->request->body);
-            }
+            h2_iq_add(m->q, stream->id, cmp, ctx);
+            register_if_needed(m);                
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                          H2_STRM_MSG(stream, "process, added to q")); 
         }
-        leave_mutex(m, acquired);
-    }
-    if (do_registration) {
-        m->need_registration = 0;
-        h2_workers_register(m->workers, m);
     }
+
+    H2_MPLX_LEAVE(m);
     return status;
 }
 
-static h2_task *pop_task(h2_mplx *m)
+static h2_task *next_stream_task(h2_mplx *m)
 {
-    h2_task *task = NULL;
     h2_stream *stream;
     int sid;
-    while (!m->aborted && !task  && (m->workers_busy < m->workers_limit)
+    while (!m->aborted && (m->tasks_active < m->limit_active)
            && (sid = h2_iq_shift(m->q)) > 0) {
         
         stream = h2_ihash_get(m->streams, sid);
         if (stream) {
             conn_rec *slave, **pslave;
-            int new_conn = 0;
 
             pslave = (conn_rec **)apr_array_pop(m->spare_slaves);
             if (pslave) {
                 slave = *pslave;
+                slave->aborted = 0;
             }
             else {
-                slave = h2_slave_create(m->c, m->pool, NULL);
-                new_conn = 1;
+                slave = h2_slave_create(m->c, stream->id, m->pool);
             }
             
-            slave->sbh = m->c->sbh;
-            slave->aborted = 0;
-            task = h2_task_create(slave, stream->request, stream->input, m);
-            h2_ihash_add(m->tasks, task);
-            
-            m->c->keepalives++;
-            apr_table_setn(slave->notes, H2_TASK_ID_NOTE, task->id);
-            if (new_conn) {
-                h2_slave_run_pre_connection(slave, ap_get_conn_socket(slave));
-            }
-            stream->started = 1;
-            task->worker_started = 1;
-            task->started_at = apr_time_now();
-            if (sid > m->max_stream_started) {
-                m->max_stream_started = sid;
-            }
+            if (!stream->task) {
 
-            if (stream->input) {
-                h2_beam_timeout_set(stream->input, m->stream_timeout);
-                h2_beam_on_consumed(stream->input, stream_input_consumed, m);
-                h2_beam_on_file_beam(stream->input, can_beam_file, m);
-                h2_beam_mutex_set(stream->input, beam_enter, task->cond, m);
+                if (sid > m->max_stream_started) {
+                    m->max_stream_started = sid;
+                }
+                if (stream->input) {
+                    h2_beam_on_consumed(stream->input, stream_input_ev, 
+                                        stream_input_consumed, stream);
+                }
+                
+                stream->task = h2_task_create(slave, stream->id, 
+                                              stream->request, m, stream->input, 
+                                              stream->session->s->timeout,
+                                              m->stream_max_mem);
+                if (!stream->task) {
+                    ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, slave,
+                                  H2_STRM_LOG(APLOGNO(02941), stream, 
+                                  "create task"));
+                    return NULL;
+                }
+                
             }
-
-            ++m->workers_busy;
+            
+            ++m->tasks_active;
+            return stream->task;
         }
     }
-    return task;
+    return NULL;
 }
 
-h2_task *h2_mplx_pop_task(h2_mplx *m, int *has_more)
+apr_status_t h2_mplx_pop_task(h2_mplx *m, h2_task **ptask)
 {
-    h2_task *task = NULL;
-    apr_status_t status;
-    int acquired;
+    apr_status_t rv = APR_EOF;
     
-    AP_DEBUG_ASSERT(m);
-    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
-        if (m->aborted) {
-            *has_more = 0;
-        }
-        else {
-            task = pop_task(m);
-            *has_more = !h2_iq_empty(m->q);
-        }
-        
-        if (has_more && !task) {
-            m->need_registration = 1;
-        }
-        leave_mutex(m, acquired);
+    *ptask = NULL;
+    if (APR_SUCCESS != (rv = apr_thread_mutex_lock(m->lock))) {
+        return rv;
+    }
+    
+    if (m->aborted) {
+        rv = APR_EOF;
     }
-    return task;
+    else {
+        *ptask = next_stream_task(m);
+        rv = (*ptask != NULL && !h2_iq_empty(m->q))? APR_EAGAIN : APR_SUCCESS;
+    }
+    if (APR_EAGAIN != rv) {
+        m->is_registered = 0; /* h2_workers will discard this mplx */
+    }
+    H2_MPLX_LEAVE(m);
+    return rv;
 }
 
 static void task_done(h2_mplx *m, h2_task *task, h2_req_engine *ngn)
 {
+    h2_stream *stream;
+    
     if (task->frozen) {
         /* this task was handed over to an engine for processing 
          * and the original worker has finished. That means the 
          * engine may start processing now. */
         h2_task_thaw(task);
-        /* we do not want the task to block on writing response
-         * bodies into the mplx. */
-        h2_task_set_io_blocking(task, 0);
         apr_thread_cond_broadcast(m->task_thawed);
         return;
     }
-    else {
-        h2_stream *stream;
         
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                      "h2_mplx(%ld): task(%s) done", m->id, task->id);
-        out_close(m, task);
-        stream = h2_ihash_get(m->streams, task->stream_id);
-        
-        if (ngn) {
-            apr_off_t bytes = 0;
-            if (task->output.beam) {
-                h2_beam_send(task->output.beam, NULL, APR_NONBLOCK_READ);
-                bytes += h2_beam_get_buffered(task->output.beam);
-            }
-            if (bytes > 0) {
-                /* we need to report consumed and current buffered output
-                 * to the engine. The request will be streamed out or cancelled,
-                 * no more data is coming from it and the engine should update
-                 * its calculations before we destroy this information. */
-                h2_req_engine_out_consumed(ngn, task->c, bytes);
-            }
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                  "h2_mplx(%ld): task(%s) done", m->id, task->id);
+    out_close(m, task);
+    
+    if (ngn) {
+        apr_off_t bytes = 0;
+        h2_beam_send(task->output.beam, NULL, APR_NONBLOCK_READ);
+        bytes += h2_beam_get_buffered(task->output.beam);
+        if (bytes > 0) {
+            /* we need to report consumed and current buffered output
+             * to the engine. The request will be streamed out or cancelled,
+             * no more data is coming from it and the engine should update
+             * its calculations before we destroy this information. */
+            h2_req_engine_out_consumed(ngn, task->c, bytes);
         }
-        
-        if (task->engine) {
-            if (!h2_req_engine_is_shutdown(task->engine)) {
-                ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c,
-                              "h2_mplx(%ld): task(%s) has not-shutdown "
-                              "engine(%s)", m->id, task->id, 
-                              h2_req_engine_get_id(task->engine));
-            }
-            h2_ngn_shed_done_ngn(m->ngn_shed, task->engine);
+    }
+    
+    if (task->engine) {
+        if (!m->aborted && !task->c->aborted 
+            && !h2_req_engine_is_shutdown(task->engine)) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c, APLOGNO(10022)
+                          "h2_mplx(%ld): task(%s) has not-shutdown "
+                          "engine(%s)", m->id, task->id, 
+                          h2_req_engine_get_id(task->engine));
+        }
+        h2_ngn_shed_done_ngn(m->ngn_shed, task->engine);
+    }
+    
+    task->worker_done = 1;
+    task->done_at = apr_time_now();
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
+                  "h2_mplx(%s): request done, %f ms elapsed", task->id, 
+                  (task->done_at - task->started_at) / 1000.0);
+    
+    if (task->started_at > m->last_idle_block) {
+        /* this task finished without causing an 'idle block', e.g.
+         * a block by flow control.
+         */
+        if (task->done_at- m->last_limit_change >= m->limit_change_interval
+            && m->limit_active < m->max_active) {
+            /* Well behaving stream, allow it more workers */
+            m->limit_active = H2MIN(m->limit_active * 2, 
+                                     m->max_active);
+            m->last_limit_change = task->done_at;
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                          "h2_mplx(%ld): increase worker limit to %d",
+                          m->id, m->limit_active);
         }
-        
-        if (!m->aborted && stream && m->redo_tasks
-            && h2_ihash_get(m->redo_tasks, task->stream_id)) {
+    }
+    
+    stream = h2_ihash_get(m->streams, task->stream_id);
+    if (stream) {
+        /* stream not done yet. */
+        if (!m->aborted && h2_ihash_get(m->sredo, stream->id)) {
             /* reset and schedule again */
             h2_task_redo(task);
-            h2_ihash_remove(m->redo_tasks, task->stream_id);
-            h2_iq_add(m->q, task->stream_id, NULL, NULL);
-            return;
-        }
-        
-        task->worker_done = 1;
-        task->done_at = apr_time_now();
-        if (task->output.beam) {
-            h2_beam_on_consumed(task->output.beam, NULL, NULL);
-            h2_beam_mutex_set(task->output.beam, NULL, NULL, NULL);
-        }
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                      "h2_mplx(%s): request done, %f ms elapsed", task->id, 
-                      (task->done_at - task->started_at) / 1000.0);
-        if (task->started_at > m->last_idle_block) {
-            /* this task finished without causing an 'idle block', e.g.
-             * a block by flow control.
-             */
-            if (task->done_at- 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 = task->done_at;
-                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 (stream) {
-            /* hang around until the stream deregisters */
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                          "h2_mplx(%s): task_done, stream still open", 
-                          task->id);
-            if (h2_stream_is_suspended(stream)) {
-                /* more data will not arrive, resume the stream */
-                h2_ihash_add(m->sresume, stream);
-                have_out_data_for(m, stream->id);
-            }
+            h2_ihash_remove(m->sredo, stream->id);
+            h2_iq_add(m->q, stream->id, NULL, NULL);
         }
         else {
-            /* stream no longer active, was it placed in hold? */
-            stream = h2_ihash_get(m->shold, task->stream_id);
-            if (stream) {
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                              "h2_mplx(%s): task_done, stream in hold", 
-                              task->id);
-                /* We cannot destroy the stream here since this is 
-                 * called from a worker thread and freeing memory pools
-                 * is only safe in the only thread using it (and its
-                 * parent pool / allocator) */
-                h2_ihash_remove(m->shold, stream->id);
-                h2_ihash_add(m->spurge, stream);
-            }
-            else {
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
-                              "h2_mplx(%s): task_done, stream not found", 
-                              task->id);
-                task_destroy(m, task, 0);
-            }
-            
-            if (m->join_wait) {
-                apr_thread_cond_signal(m->join_wait);
+            /* stream not cleaned up, stay around */
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
+                          H2_STRM_MSG(stream, "task_done, stream open")); 
+            if (stream->input) {
+                h2_beam_leave(stream->input);
             }
+
+            /* more data will not arrive, resume the stream */
+            check_data_for(m, stream, 0);            
         }
     }
+    else if ((stream = h2_ihash_get(m->shold, task->stream_id)) != NULL) {
+        /* stream is done, was just waiting for this. */
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
+                      H2_STRM_MSG(stream, "task_done, in hold"));
+        if (stream->input) {
+            h2_beam_leave(stream->input);
+        }
+        stream_joined(m, stream);
+    }
+    else if ((stream = h2_ihash_get(m->spurge, task->stream_id)) != NULL) {
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c,   
+                      H2_STRM_LOG(APLOGNO(03517), stream, "already in spurge"));
+        ap_assert("stream should not be in spurge" == NULL);
+    }
+    else {
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, m->c, APLOGNO(03518)
+                      "h2_mplx(%s): task_done, stream not found", 
+                      task->id);
+        ap_assert("stream should still be available" == NULL);
+    }
 }
 
 void h2_mplx_task_done(h2_mplx *m, h2_task *task, h2_task **ptask)
 {
-    int acquired;
+    H2_MPLX_ENTER_ALWAYS(m);
+
+    task_done(m, task, NULL);
+    --m->tasks_active;
     
-    if (enter_mutex(m, &acquired) == APR_SUCCESS) {
-        task_done(m, task, NULL);
-        --m->workers_busy;
-        if (ptask) {
-            /* caller wants another task */
-            *ptask = pop_task(m);
-        }
-        leave_mutex(m, acquired);
+    if (m->join_wait) {
+        apr_thread_cond_signal(m->join_wait);
+    }
+    if (ptask) {
+        /* caller wants another task */
+        *ptask = next_stream_task(m);
     }
+    register_if_needed(m);
+
+    H2_MPLX_LEAVE(m);
 }
 
 /*******************************************************************************
@@ -1073,74 +903,76 @@ void h2_mplx_task_done(h2_mplx *m, h2_ta
 
 static int latest_repeatable_unsubmitted_iter(void *data, void *val)
 {
-    task_iter_ctx *ctx = data;
-    h2_task *task = val;
-    if (!task->worker_done && h2_task_can_redo(task) 
-        && !h2_ihash_get(ctx->m->redo_tasks, task->stream_id)) {
-        /* this task 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->task || ctx->task->started_at < task->started_at) {
-            /* we did not have one or this one was started later */
-            ctx->task = task;
+    stream_iter_ctx *ctx = data;
+    h2_stream *stream = val;
+    
+    if (stream->task && !stream->task->worker_done 
+        && h2_task_can_redo(stream->task) 
+        && !h2_ihash_get(ctx->m->sredo, stream->id)) {
+        if (!h2_stream_is_ready(stream)) {
+            /* this task 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->stream 
+                || (ctx->stream->task->started_at < stream->task->started_at)) {
+                /* we did not have one or this one was started later */
+                ctx->stream = stream;
+            }
         }
     }
     return 1;
 }
 
-static h2_task *get_latest_repeatable_unsubmitted_task(h2_mplx *m) 
+static h2_stream *get_latest_repeatable_unsubmitted_stream(h2_mplx *m) 
 {
-    task_iter_ctx ctx;
+    stream_iter_ctx ctx;
     ctx.m = m;
-    ctx.task = NULL;
-    h2_ihash_iter(m->tasks, latest_repeatable_unsubmitted_iter, &ctx);
-    return ctx.task;
+    ctx.stream = NULL;
+    h2_ihash_iter(m->streams, latest_repeatable_unsubmitted_iter, &ctx);
+    return ctx.stream;
 }
 
 static int timed_out_busy_iter(void *data, void *val)
 {
-    task_iter_ctx *ctx = data;
-    h2_task *task = val;
-    if (!task->worker_done
-        && (ctx->now - task->started_at) > ctx->m->stream_timeout) {
+    stream_iter_ctx *ctx = data;
+    h2_stream *stream = val;
+    if (stream->task && !stream->task->worker_done
+        && (ctx->now - stream->task->started_at) > stream->task->timeout) {
         /* timed out stream occupying a worker, found */
-        ctx->task = task;
+        ctx->stream = stream;
         return 0;
     }
     return 1;
 }
 
-static h2_task *get_timed_out_busy_task(h2_mplx *m) 
+static h2_stream *get_timed_out_busy_stream(h2_mplx *m) 
 {
-    task_iter_ctx ctx;
+    stream_iter_ctx ctx;
     ctx.m = m;
-    ctx.task = NULL;
+    ctx.stream = NULL;
     ctx.now = apr_time_now();
-    h2_ihash_iter(m->tasks, timed_out_busy_iter, &ctx);
-    return ctx.task;
+    h2_ihash_iter(m->streams, timed_out_busy_iter, &ctx);
+    return ctx.stream;
 }
 
 static apr_status_t unschedule_slow_tasks(h2_mplx *m) 
 {
-    h2_task *task;
+    h2_stream *stream;
     int n;
     
-    if (!m->redo_tasks) {
-        m->redo_tasks = h2_ihash_create(m->pool, offsetof(h2_task, stream_id));
-    }
     /* 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_ihash_count(m->redo_tasks));
-    while (n > 0 && (task = get_latest_repeatable_unsubmitted_task(m))) {
-        h2_task_rst(task, H2_ERR_CANCEL);
-        h2_ihash_add(m->redo_tasks, task);
+    n = (m->tasks_active - m->limit_active - (int)h2_ihash_count(m->sredo));
+    while (n > 0 && (stream = get_latest_repeatable_unsubmitted_stream(m))) {
+        h2_task_rst(stream->task, H2_ERR_CANCEL);
+        h2_ihash_add(m->sredo, stream);
         --n;
     }
     
-    if ((m->workers_busy - h2_ihash_count(m->redo_tasks)) > m->workers_limit) {
-        task = get_timed_out_busy_task(m);
-        if (task) {
+    if ((m->tasks_active - h2_ihash_count(m->sredo)) > m->limit_active) {
+        h2_stream *stream = get_timed_out_busy_stream(m);
+        if (stream) {
             /* Too many busy workers, unable to cancel enough streams
              * and with a busy, timed out stream, we tell the client
              * to go away... */
@@ -1154,11 +986,13 @@ apr_status_t h2_mplx_idle(h2_mplx *m)
 {
     apr_status_t status = APR_SUCCESS;
     apr_time_t now;            
-    int acquired;
+    apr_size_t scount;
     
-    if (enter_mutex(m, &acquired) == APR_SUCCESS) {
-        apr_size_t scount = h2_ihash_count(m->streams);
-        if (scount > 0 && m->workers_busy) {
+    H2_MPLX_ENTER(m);
+
+    scount = h2_ihash_count(m->streams);
+    if (scount > 0) {
+        if (m->tasks_active) {
             /* If we have streams in connection state 'IDLE', meaning
              * all streams are ready to sent data out, but lack
              * WINDOW_UPDATEs. 
@@ -1173,32 +1007,68 @@ apr_status_t h2_mplx_idle(h2_mplx *m)
              */
             now = apr_time_now();
             m->last_idle_block = now;
-            if (m->workers_limit > 2 
+            if (m->limit_active > 2 
                 && now - m->last_limit_change >= m->limit_change_interval) {
-                if (m->workers_limit > 16) {
-                    m->workers_limit = 16;
+                if (m->limit_active > 16) {
+                    m->limit_active = 16;
                 }
-                else if (m->workers_limit > 8) {
-                    m->workers_limit = 8;
+                else if (m->limit_active > 8) {
+                    m->limit_active = 8;
                 }
-                else if (m->workers_limit > 4) {
-                    m->workers_limit = 4;
+                else if (m->limit_active > 4) {
+                    m->limit_active = 4;
                 }
-                else if (m->workers_limit > 2) {
-                    m->workers_limit = 2;
+                else if (m->limit_active > 2) {
+                    m->limit_active = 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);
+                              m->id, m->limit_active);
             }
             
-            if (m->workers_busy > m->workers_limit) {
+            if (m->tasks_active > m->limit_active) {
                 status = unschedule_slow_tasks(m);
             }
         }
-        leave_mutex(m, acquired);
+        else if (!h2_iq_empty(m->q)) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                          "h2_mplx(%ld): idle, but %d streams to process",
+                          m->id, (int)h2_iq_count(m->q));
+            status = APR_EAGAIN;
+        }
+        else {
+            /* idle, have streams, but no tasks active. what are we waiting for?
+             * WINDOW_UPDATEs from client? */
+            h2_stream *stream = NULL;
+            
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                          "h2_mplx(%ld): idle, no tasks ongoing, %d streams",
+                          m->id, (int)h2_ihash_count(m->streams));
+            h2_ihash_shift(m->streams, (void**)&stream, 1);
+            if (stream) {
+                h2_ihash_add(m->streams, stream);
+                if (stream->output && !stream->out_checked) {
+                    /* FIXME: this looks like a race between the session thinking
+                     * it is idle and the EOF on a stream not being sent.
+                     * Signal to caller to leave IDLE state.
+                     */
+                    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c,
+                                  H2_STRM_MSG(stream, "output closed=%d, mplx idle"
+                                              ", out has %ld bytes buffered"),
+                                  h2_beam_is_closed(stream->output),
+                                  (long)h2_beam_get_buffered(stream->output));
+                    h2_ihash_add(m->streams, stream);
+                    check_data_for(m, stream, 0);
+                    stream->out_checked = 1;
+                    status = APR_EAGAIN;
+                }
+            }
+        }
     }
+    register_if_needed(m);
+
+    H2_MPLX_LEAVE(m);
     return status;
 }
 
@@ -1215,9 +1085,9 @@ typedef struct {
 static int ngn_update_window(void *ctx, void *val)
 {
     ngn_update_ctx *uctx = ctx;
-    h2_task *task = val;
-    if (task && task->assigned == uctx->ngn
-        && output_consumed_signal(uctx->m, task)) {
+    h2_stream *stream = val;
+    if (stream->task && stream->task->assigned == uctx->ngn
+        && output_consumed_signal(uctx->m, stream->task)) {
         ++uctx->streams_updated;
     }
     return 1;
@@ -1230,7 +1100,7 @@ static apr_status_t ngn_out_update_windo
     ctx.m = m;
     ctx.ngn = ngn;
     ctx.streams_updated = 0;
-    h2_ihash_iter(m->tasks, ngn_update_window, &ctx);
+    h2_ihash_iter(m->streams, ngn_update_window, &ctx);
     
     return ctx.streams_updated? APR_SUCCESS : APR_EAGAIN;
 }
@@ -1242,92 +1112,99 @@ apr_status_t h2_mplx_req_engine_push(con
     apr_status_t status;
     h2_mplx *m;
     h2_task *task;
-    int acquired;
+    h2_stream *stream;
     
     task = h2_ctx_rget_task(r);
     if (!task) {
         return APR_ECONNABORTED;
     }
     m = task->mplx;
-    task->r = r;
     
-    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
-        h2_stream *stream = h2_ihash_get(m->streams, task->stream_id);
-        
-        if (stream) {
-            status = h2_ngn_shed_push_task(m->ngn_shed, ngn_type, task, einit);
-        }
-        else {
-            status = APR_ECONNABORTED;
-        }
-        leave_mutex(m, acquired);
+    H2_MPLX_ENTER(m);
+
+    stream = h2_ihash_get(m->streams, task->stream_id);
+    if (stream) {
+        status = h2_ngn_shed_push_request(m->ngn_shed, ngn_type, r, einit);
     }
+    else {
+        status = APR_ECONNABORTED;
+    }
+
+    H2_MPLX_LEAVE(m);
     return status;
 }
 
 apr_status_t h2_mplx_req_engine_pull(h2_req_engine *ngn, 
                                      apr_read_type_e block, 
-                                     apr_uint32_t capacity, 
+                                     int capacity, 
                                      request_rec **pr)
 {   
     h2_ngn_shed *shed = h2_ngn_shed_get_shed(ngn);
     h2_mplx *m = h2_ngn_shed_get_ctx(shed);
     apr_status_t status;
-    h2_task *task = NULL;
-    int acquired;
+    int want_shutdown;
     
-    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
-        int want_shutdown = (block == APR_BLOCK_READ);
+    H2_MPLX_ENTER(m);
 
-        /* Take this opportunity to update output consummation 
-         * for this engine */
-        ngn_out_update_windows(m, ngn);
-        
-        if (want_shutdown && !h2_iq_empty(m->q)) {
-            /* For a blocking read, check first if requests are to be
-             * had and, if not, wait a short while before doing the
-             * blocking, and if unsuccessful, terminating read.
-             */
-            status = h2_ngn_shed_pull_task(shed, ngn, capacity, 1, &task);
-            if (APR_STATUS_IS_EAGAIN(status)) {
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
-                              "h2_mplx(%ld): start block engine pull", m->id);
-                apr_thread_cond_timedwait(m->task_thawed, m->lock, 
-                                          apr_time_from_msec(20));
-                status = h2_ngn_shed_pull_task(shed, ngn, capacity, 1, &task);
-            }
-        }
-        else {
-            status = h2_ngn_shed_pull_task(shed, ngn, capacity,
-                                           want_shutdown, &task);
+    want_shutdown = (block == APR_BLOCK_READ);
+
+    /* Take this opportunity to update output consummation 
+     * for this engine */
+    ngn_out_update_windows(m, ngn);
+    
+    if (want_shutdown && !h2_iq_empty(m->q)) {
+        /* For a blocking read, check first if requests are to be
+         * had and, if not, wait a short while before doing the
+         * blocking, and if unsuccessful, terminating read.
+         */
+        status = h2_ngn_shed_pull_request(shed, ngn, capacity, 1, pr);
+        if (APR_STATUS_IS_EAGAIN(status)) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, m->c,
+                          "h2_mplx(%ld): start block engine pull", m->id);
+            apr_thread_cond_timedwait(m->task_thawed, m->lock, 
+                                      apr_time_from_msec(20));
+            status = h2_ngn_shed_pull_request(shed, ngn, capacity, 1, pr);
         }
-        leave_mutex(m, acquired);
     }
-    *pr = task? task->r : NULL;
+    else {
+        status = h2_ngn_shed_pull_request(shed, ngn, capacity,
+                                          want_shutdown, pr);
+    }
+
+    H2_MPLX_LEAVE(m);
     return status;
 }
  
-void h2_mplx_req_engine_done(h2_req_engine *ngn, conn_rec *r_conn)
+void h2_mplx_req_engine_done(h2_req_engine *ngn, conn_rec *r_conn,
+                             apr_status_t status)
 {
     h2_task *task = h2_ctx_cget_task(r_conn);
     
     if (task) {
         h2_mplx *m = task->mplx;
-        int acquired;
+        h2_stream *stream;
 
-        if (enter_mutex(m, &acquired) == APR_SUCCESS) {
-            ngn_out_update_windows(m, ngn);
-            h2_ngn_shed_done_task(m->ngn_shed, ngn, task);
-            if (task->engine) { 
-                /* cannot report that as done until engine returns */
-            }
-            else {
-                task_done(m, task, ngn);
-            }
-            /* Take this opportunity to update output consummation 
-             * for this engine */
-            leave_mutex(m, acquired);
+        H2_MPLX_ENTER_ALWAYS(m);
+
+        stream = h2_ihash_get(m->streams, task->stream_id);
+        
+        ngn_out_update_windows(m, ngn);
+        h2_ngn_shed_done_task(m->ngn_shed, ngn, task);
+        
+        if (status != APR_SUCCESS && stream 
+            && h2_task_can_redo(task) 
+            && !h2_ihash_get(m->sredo, stream->id)) {
+            h2_ihash_add(m->sredo, stream);
+        }
+
+        if (task->engine) { 
+            /* cannot report that as done until engine returns */
         }
+        else {
+            task_done(m, task, ngn);
+        }
+
+        H2_MPLX_LEAVE(m);
     }
 }
 
@@ -1335,124 +1212,59 @@ void h2_mplx_req_engine_done(h2_req_engi
  * mplx master events dispatching
  ******************************************************************************/
 
-static int update_window(void *ctx, void *val)
+int h2_mplx_has_master_events(h2_mplx *m)
 {
-    input_consumed_signal(ctx, val);
-    return 1;
+    return apr_atomic_read32(&m->event_pending) > 0;
 }
 
 apr_status_t h2_mplx_dispatch_master_events(h2_mplx *m, 
                                             stream_ev_callback *on_resume, 
-                                            stream_ev_callback *on_response, 
                                             void *on_ctx)
 {
-    apr_status_t status;
-    int acquired;
-    int streams[32];
     h2_stream *stream;
-    h2_task *task;
-    size_t i, n;
+    int n, id;
     
-    AP_DEBUG_ASSERT(m);
-    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, m->c, 
-                      "h2_mplx(%ld): dispatch events", m->id);
-                      
-        /* update input windows for streams */
-        h2_ihash_iter(m->streams, update_window, m);
-
-        if (on_response && !h2_ihash_empty(m->sready)) {
-            n = h2_ihash_ishift(m->sready, streams, H2_ALEN(streams));
-            for (i = 0; i < n; ++i) {
-                stream = h2_ihash_get(m->streams, streams[i]);
-                if (!stream) {
-                    continue;
-                }
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, m->c, 
-                              "h2_mplx(%ld-%d): on_response", 
-                              m->id, stream->id);
-                task = h2_ihash_get(m->tasks, stream->id);
-                if (task) {
-                    task->submitted = 1;
-                    if (task->rst_error) {
-                        h2_stream_rst(stream, task->rst_error);
-                    }
-                    else {
-                        AP_DEBUG_ASSERT(task->response);
-                        h2_stream_set_response(stream, task->response, task->output.beam);
-                    }
-                }
-                else {
-                    /* We have the stream ready without a task. This happens
-                     * when we fail streams early. A response should already
-                     * be present.  */
-                    AP_DEBUG_ASSERT(stream->response || stream->rst_error);
-                }
-                status = on_response(on_ctx, stream->id);
-            }
-        }
-
-        if (on_resume && !h2_ihash_empty(m->sresume)) {
-            n = h2_ihash_ishift(m->sresume, streams, H2_ALEN(streams));
-            for (i = 0; i < n; ++i) {
-                stream = h2_ihash_get(m->streams, streams[i]);
-                if (!stream) {
-                    continue;
-                }
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, m->c, 
-                              "h2_mplx(%ld-%d): on_resume", 
-                              m->id, stream->id);
-                h2_stream_set_suspended(stream, 0);
-                status = on_resume(on_ctx, stream->id);
-            }
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, m->c, 
+                  "h2_mplx(%ld): dispatch events", m->id);        
+    apr_atomic_set32(&m->event_pending, 0);
+
+    /* update input windows for streams */
+    h2_ihash_iter(m->streams, report_consumption_iter, m);    
+    purge_streams(m, 1);
+    
+    n = h2_ififo_count(m->readyq);
+    while (n > 0 
+           && (h2_ififo_try_pull(m->readyq, &id) == APR_SUCCESS)) {
+        --n;
+        stream = h2_ihash_get(m->streams, id);
+        if (stream) {
+            on_resume(on_ctx, stream);
         }
-        
-        leave_mutex(m, acquired);
     }
-    return status;
+    
+    return APR_SUCCESS;
 }
 
-static void output_produced(void *ctx, h2_bucket_beam *beam, apr_off_t bytes)
+apr_status_t h2_mplx_keep_active(h2_mplx *m, h2_stream *stream)
 {
-    h2_mplx *m = ctx;
-    apr_status_t status;
-    h2_stream *stream;
-    int acquired;
-    
-    AP_DEBUG_ASSERT(m);
-    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
-        stream = h2_ihash_get(m->streams, beam->id);
-        if (stream && h2_stream_is_suspended(stream)) {
-            h2_ihash_add(m->sresume, stream);
-            h2_beam_on_produced(beam, NULL, NULL);
-            have_out_data_for(m, beam->id);
-        }
-        leave_mutex(m, acquired);
-    }
+    check_data_for(m, stream, 1);
+    return APR_SUCCESS;
 }
 
-apr_status_t h2_mplx_suspend_stream(h2_mplx *m, int stream_id)
+int h2_mplx_awaits_data(h2_mplx *m)
 {
-    apr_status_t status;
-    h2_stream *stream;
-    h2_task *task;
-    int acquired;
-    
-    AP_DEBUG_ASSERT(m);
-    if ((status = enter_mutex(m, &acquired)) == APR_SUCCESS) {
-        stream = h2_ihash_get(m->streams, stream_id);
-        if (stream) {
-            h2_stream_set_suspended(stream, 1);
-            task = h2_ihash_get(m->tasks, stream->id);
-            if (stream->started && (!task || task->worker_done)) {
-                h2_ihash_add(m->sresume, stream);
-            }
-            else {
-                /* register callback so that we can resume on new output */
-                h2_beam_on_produced(task->output.beam, output_produced, m);
-            }
-        }
-        leave_mutex(m, acquired);
+    int waiting = 1;
+     
+    H2_MPLX_ENTER_ALWAYS(m);
+
+    if (h2_ihash_empty(m->streams)) {
+        waiting = 0;
     }
-    return status;
+    else if (!m->tasks_active && !h2_ififo_count(m->readyq)
+             && h2_iq_empty(m->q)) {
+        waiting = 0;
+    }
+
+    H2_MPLX_LEAVE(m);
+    return waiting;
 }
diff -up --new-file httpd-2.4.23/modules/http2/h2_mplx.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_mplx.h
--- httpd-2.4.23/modules/http2/h2_mplx.h	2016-06-14 10:51:31.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_mplx.h	2017-07-06 14:58:22.000000000 +0200
@@ -40,7 +40,6 @@ struct apr_thread_cond_t;
 struct h2_bucket_beam;
 struct h2_config;
 struct h2_ihash_t;
-struct h2_response;
 struct h2_task;
 struct h2_stream;
 struct h2_request;
@@ -54,40 +53,31 @@ struct h2_req_engine;
 
 typedef struct h2_mplx h2_mplx;
 
-/**
- * Callback invoked for every stream that had input data read since
- * the last invocation.
- */
-typedef void h2_mplx_consumed_cb(void *ctx, int stream_id, apr_off_t consumed);
-
 struct h2_mplx {
     long id;
     conn_rec *c;
     apr_pool_t *pool;
-    apr_bucket_alloc_t *bucket_alloc;
+    server_rec *s;                  /* server for master conn */
 
-    APR_RING_ENTRY(h2_mplx) link;
-
-    unsigned int aborted : 1;
-    unsigned int need_registration : 1;
+    unsigned int event_pending;
+    unsigned int aborted;
+    unsigned int is_registered;     /* is registered at h2_workers */
 
     struct h2_ihash_t *streams;     /* all streams currently processing */
+    struct h2_ihash_t *sredo;       /* all streams that need to be re-started */
     struct h2_ihash_t *shold;       /* all streams done with task ongoing */
     struct h2_ihash_t *spurge;      /* all streams done, ready for destroy */
-
-    struct h2_iqueue *q;            /* all stream ids that need to be started */
-    struct h2_ihash_t *sready;      /* all streams ready for response */
-    struct h2_ihash_t *sresume;     /* all streams that can be resumed */
     
-    struct h2_ihash_t *tasks;       /* all tasks started and not destroyed */
+    struct h2_iqueue *q;            /* all stream ids that need to be started */
+    struct h2_ififo *readyq;        /* all stream ids ready for output */
+        
     struct h2_ihash_t *redo_tasks;  /* all tasks that need to be redone */
     
-    apr_uint32_t max_streams;        /* max # of concurrent streams */
-    apr_uint32_t max_stream_started; /* highest stream id that started processing */
-    apr_uint32_t workers_busy;       /* # of workers processing on this mplx */
-    apr_uint32_t workers_limit;      /* current # of workers limit, dynamic */
-    apr_uint32_t workers_def_limit;  /* default # of workers limit */
-    apr_uint32_t workers_max;        /* max, hard limit # of workers in a process */
+    int max_streams;        /* max # of concurrent streams */
+    int max_stream_started; /* highest stream id that started processing */
+    int tasks_active;       /* # of tasks being processed from this mplx */
+    int limit_active;       /* current limit on active tasks, dynamic */
+    int max_active;         /* max, hard limit # of active tasks 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 */
@@ -99,18 +89,12 @@ struct h2_mplx {
     struct apr_thread_cond_t *join_wait;
     
     apr_size_t stream_max_mem;
-    apr_interval_time_t stream_timeout;
     
     apr_pool_t *spare_io_pool;
     apr_array_header_t *spare_slaves; /* spare slave connections */
     
     struct h2_workers *workers;
-    int tx_handles_reserved;
-    apr_size_t tx_chunk_size;
     
-    h2_mplx_consumed_cb *input_consumed;
-    void *input_consumed_ctx;
-
     struct h2_ngn_shed *ngn_shed;
 };
 
@@ -128,7 +112,6 @@ apr_status_t h2_mplx_child_init(apr_pool
  */
 h2_mplx *h2_mplx_create(conn_rec *c, apr_pool_t *master, 
                         const struct h2_config *conf, 
-                        apr_interval_time_t stream_timeout,
                         struct h2_workers *workers);
 
 /**
@@ -139,15 +122,9 @@ h2_mplx *h2_mplx_create(conn_rec *c, apr
  * @param m the mplx to be released and destroyed
  * @param wait condition var to wait on for ref counter == 0
  */ 
-apr_status_t h2_mplx_release_and_join(h2_mplx *m, struct apr_thread_cond_t *wait);
-
-/**
- * Aborts the multiplexer. It will answer all future invocation with
- * APR_ECONNABORTED, leading to early termination of ongoing streams.
- */
-void h2_mplx_abort(h2_mplx *mplx);
+void h2_mplx_release_and_join(h2_mplx *m, struct apr_thread_cond_t *wait);
 
-struct h2_task *h2_mplx_pop_task(h2_mplx *mplx, int *has_more);
+apr_status_t h2_mplx_pop_task(h2_mplx *m, struct h2_task **ptask);
 
 void h2_mplx_task_done(h2_mplx *m, struct h2_task *task, struct h2_task **ptask);
 
@@ -156,21 +133,24 @@ void h2_mplx_task_done(h2_mplx *m, struc
  * but let the ongoing ones finish normally.
  * @return the highest stream id being/been processed
  */
-apr_uint32_t h2_mplx_shutdown(h2_mplx *m);
+int h2_mplx_shutdown(h2_mplx *m);
+
+int h2_mplx_is_busy(h2_mplx *m);
 
 /*******************************************************************************
  * IO lifetime of streams.
  ******************************************************************************/
 
+struct h2_stream *h2_mplx_stream_get(h2_mplx *m, int id);
+
 /**
- * Notifies mplx that a stream has finished processing.
+ * Notifies mplx that a stream has been completely handled on the main
+ * connection and is ready for cleanup.
  * 
  * @param m the mplx itself
- * @param stream the id of the stream being done
- * @param rst_error if != 0, the stream was reset with the error given
- *
+ * @param stream the stream ready for cleanup
  */
-apr_status_t h2_mplx_stream_done(h2_mplx *m, struct h2_stream *stream);
+apr_status_t h2_mplx_stream_cleanup(h2_mplx *m, struct h2_stream *stream);
 
 /**
  * Waits on output data from any stream in this session to become available. 
@@ -179,6 +159,8 @@ apr_status_t h2_mplx_stream_done(h2_mplx
 apr_status_t h2_mplx_out_trywait(h2_mplx *m, apr_interval_time_t timeout,
                                  struct apr_thread_cond_t *iowait);
 
+apr_status_t h2_mplx_keep_active(h2_mplx *m, struct h2_stream *stream);
+
 /*******************************************************************************
  * Stream processing.
  ******************************************************************************/
@@ -204,31 +186,29 @@ apr_status_t h2_mplx_process(h2_mplx *m,
  */
 apr_status_t h2_mplx_reprioritize(h2_mplx *m, h2_stream_pri_cmp *cmp, void *ctx);
 
+typedef apr_status_t stream_ev_callback(void *ctx, struct h2_stream *stream);
+
 /**
- * Register a callback for the amount of input data consumed per stream. The
- * will only ever be invoked from the thread creating this h2_mplx, e.g. when
- * calls from that thread into this h2_mplx are made.
- *
- * @param m the multiplexer to register the callback at
- * @param cb the function to invoke
- * @param ctx user supplied argument to invocation.
+ * Check if the multiplexer has events for the master connection pending.
+ * @return != 0 iff there are events pending
  */
-void h2_mplx_set_consumed_cb(h2_mplx *m, h2_mplx_consumed_cb *cb, void *ctx);
-
-
-typedef apr_status_t stream_ev_callback(void *ctx, int stream_id);
+int h2_mplx_has_master_events(h2_mplx *m);
 
 /**
  * Dispatch events for the master connection, such as
- * - resume: new output data has arrived for a suspended stream
- * - response: the response for a stream is ready
+ ± @param m the multiplexer
+ * @param on_resume new output data has arrived for a suspended stream 
+ * @param ctx user supplied argument to invocation.
  */
 apr_status_t h2_mplx_dispatch_master_events(h2_mplx *m, 
                                             stream_ev_callback *on_resume, 
-                                            stream_ev_callback *on_response, 
                                             void *ctx);
 
-apr_status_t h2_mplx_suspend_stream(h2_mplx *m, int stream_id);
+int h2_mplx_awaits_data(h2_mplx *m);
+
+typedef int h2_mplx_stream_cb(struct h2_stream *s, void *ctx);
+
+apr_status_t h2_mplx_stream_do(h2_mplx *m, h2_mplx_stream_cb *cb, void *ctx);
 
 /*******************************************************************************
  * Output handling of streams.
@@ -238,7 +218,7 @@ apr_status_t h2_mplx_suspend_stream(h2_m
  * Opens the output for the given stream with the specified response.
  */
 apr_status_t h2_mplx_out_open(h2_mplx *mplx, int stream_id,
-                              struct h2_response *response);
+                              struct h2_bucket_beam *beam);
 
 /*******************************************************************************
  * h2_mplx list Manipulation.
@@ -331,7 +311,7 @@ typedef apr_status_t h2_mplx_req_engine_
                                              const char *id, 
                                              const char *type,
                                              apr_pool_t *pool, 
-                                             apr_uint32_t req_buffer_size,
+                                             apr_size_t req_buffer_size,
                                              request_rec *r,
                                              h2_output_consumed **pconsumed,
                                              void **pbaton);
@@ -341,8 +321,9 @@ apr_status_t h2_mplx_req_engine_push(con
                                      h2_mplx_req_engine_init *einit);
 apr_status_t h2_mplx_req_engine_pull(struct h2_req_engine *ngn, 
                                      apr_read_type_e block, 
-                                     apr_uint32_t capacity, 
+                                     int capacity, 
                                      request_rec **pr);
-void h2_mplx_req_engine_done(struct h2_req_engine *ngn, conn_rec *r_conn);
+void h2_mplx_req_engine_done(struct h2_req_engine *ngn, conn_rec *r_conn,
+                             apr_status_t status);
 
 #endif /* defined(__mod_h2__h2_mplx__) */
diff -up --new-file httpd-2.4.23/modules/http2/h2_ngn_shed.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_ngn_shed.c
--- httpd-2.4.23/modules/http2/h2_ngn_shed.c	2016-06-22 15:30:24.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_ngn_shed.c	2017-04-10 17:04:55.000000000 +0200
@@ -35,7 +35,6 @@
 #include "h2_ctx.h"
 #include "h2_h2.h"
 #include "h2_mplx.h"
-#include "h2_response.h"
 #include "h2_request.h"
 #include "h2_task.h"
 #include "h2_util.h"
@@ -46,6 +45,7 @@ typedef struct h2_ngn_entry h2_ngn_entry
 struct h2_ngn_entry {
     APR_RING_ENTRY(h2_ngn_entry) link;
     h2_task *task;
+    request_rec *r;
 };
 
 #define H2_NGN_ENTRY_NEXT(e)	APR_RING_NEXT((e), link)
@@ -72,17 +72,17 @@ struct h2_req_engine {
     const char *type;      /* name of the engine type */
     apr_pool_t *pool;      /* pool for engine specific allocations */
     conn_rec *c;           /* connection this engine is assigned to */
-    h2_task *task;         /* the task this engine is base on, running in */
+    h2_task *task;         /* the task this engine is based on, running in */
     h2_ngn_shed *shed;
 
     unsigned int shutdown : 1; /* engine is being shut down */
     unsigned int done : 1;     /* engine has finished */
 
     APR_RING_HEAD(h2_req_entries, h2_ngn_entry) entries;
-    apr_uint32_t capacity;     /* maximum concurrent requests */
-    apr_uint32_t no_assigned;  /* # of assigned requests */
-    apr_uint32_t no_live;      /* # of live */
-    apr_uint32_t no_finished;  /* # of finished */
+    int capacity;     /* maximum concurrent requests */
+    int no_assigned;  /* # of assigned requests */
+    int no_live;      /* # of live */
+    int no_finished;  /* # of finished */
     
     h2_output_consumed *out_consumed;
     void *out_consumed_ctx;
@@ -107,8 +107,8 @@ void h2_req_engine_out_consumed(h2_req_e
 }
 
 h2_ngn_shed *h2_ngn_shed_create(apr_pool_t *pool, conn_rec *c,
-                                apr_uint32_t default_capacity, 
-                                apr_uint32_t req_buffer_size)
+                                int default_capacity, 
+                                apr_size_t req_buffer_size)
 {
     h2_ngn_shed *shed;
     
@@ -144,30 +144,50 @@ void h2_ngn_shed_abort(h2_ngn_shed *shed
     shed->aborted = 1;
 }
 
-static void ngn_add_task(h2_req_engine *ngn, h2_task *task)
+static void ngn_add_task(h2_req_engine *ngn, h2_task *task, request_rec *r)
 {
     h2_ngn_entry *entry = apr_pcalloc(task->pool, sizeof(*entry));
     APR_RING_ELEM_INIT(entry, link);
     entry->task = task;
+    entry->r = r;
     H2_REQ_ENTRIES_INSERT_TAIL(&ngn->entries, entry);
+    ngn->no_assigned++;
 }
 
 
-apr_status_t h2_ngn_shed_push_task(h2_ngn_shed *shed, const char *ngn_type, 
-                                   h2_task *task, http2_req_engine_init *einit) 
+apr_status_t h2_ngn_shed_push_request(h2_ngn_shed *shed, const char *ngn_type, 
+                                      request_rec *r, 
+                                      http2_req_engine_init *einit) 
 {
     h2_req_engine *ngn;
+    h2_task *task = h2_ctx_rget_task(r);
 
-    AP_DEBUG_ASSERT(shed);
-    
+    ap_assert(task);
     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, shed->c,
                   "h2_ngn_shed(%ld): PUSHing request (task=%s)", shed->c->id, 
                   task->id);
-    if (task->ser_headers) {
+    if (task->request->serialize) {
         /* Max compatibility, deny processing of this */
         return APR_EOF;
     }
     
+    if (task->assigned) {
+        --task->assigned->no_assigned;
+        --task->assigned->no_live;
+        task->assigned = NULL;
+    }
+    
+    if (task->engine) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c, 
+                      "h2_ngn_shed(%ld): push task(%s) hosting engine %s " 
+                      "already with %d tasks", 
+                      shed->c->id, task->id, task->engine->id,
+                      task->engine->no_assigned);
+        task->assigned = task->engine;
+        ngn_add_task(task->engine, task, r);
+        return APR_SUCCESS;
+    }
+    
     ngn = apr_hash_get(shed->ngns, ngn_type, APR_HASH_KEY_STRING);
     if (ngn && !ngn->shutdown) {
         /* this task will be processed in another thread,
@@ -175,12 +195,10 @@ apr_status_t h2_ngn_shed_push_task(h2_ng
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
                       "h2_ngn_shed(%ld): pushing request %s to %s", 
                       shed->c->id, task->id, ngn->id);
-        if (!h2_task_is_detached(task)) {
+        if (!h2_task_has_thawed(task)) {
             h2_task_freeze(task);
         }
-        /* FIXME: sometimes ngn is garbage, probly alread freed */
-        ngn_add_task(ngn, task);
-        ngn->no_assigned++;
+        ngn_add_task(ngn, task, r);
         return APR_SUCCESS;
     }
     
@@ -202,13 +220,13 @@ apr_status_t h2_ngn_shed_push_task(h2_ng
         APR_RING_INIT(&newngn->entries, h2_ngn_entry, link);
         
         status = einit(newngn, newngn->id, newngn->type, newngn->pool,
-                       shed->req_buffer_size, task->r, 
+                       shed->req_buffer_size, r,
                        &newngn->out_consumed, &newngn->out_consumed_ctx);
+        
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, task->c, APLOGNO(03395)
                       "h2_ngn_shed(%ld): create engine %s (%s)", 
                       shed->c->id, newngn->id, newngn->type);
         if (status == APR_SUCCESS) {
-            AP_DEBUG_ASSERT(task->engine == NULL);
             newngn->task = task;
             task->engine = newngn;
             task->assigned = newngn;
@@ -225,7 +243,7 @@ static h2_ngn_entry *pop_detached(h2_req
     for (entry = H2_REQ_ENTRIES_FIRST(&ngn->entries);
          entry != H2_REQ_ENTRIES_SENTINEL(&ngn->entries);
          entry = H2_NGN_ENTRY_NEXT(entry)) {
-        if (h2_task_is_detached(entry->task) 
+        if (h2_task_has_thawed(entry->task) 
             || (entry->task->engine == ngn)) {
             /* The task hosting this engine can always be pulled by it.
              * For other task, they need to become detached, e.g. no longer
@@ -237,17 +255,17 @@ static h2_ngn_entry *pop_detached(h2_req
     return NULL;
 }
 
-apr_status_t h2_ngn_shed_pull_task(h2_ngn_shed *shed, 
-                                   h2_req_engine *ngn, 
-                                   apr_uint32_t capacity, 
-                                   int want_shutdown,
-                                   h2_task **ptask)
+apr_status_t h2_ngn_shed_pull_request(h2_ngn_shed *shed, 
+                                      h2_req_engine *ngn, 
+                                      int capacity, 
+                                      int want_shutdown,
+                                      request_rec **pr)
 {   
     h2_ngn_entry *entry;
     
-    AP_DEBUG_ASSERT(ngn);
-    *ptask = NULL;
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, shed->c, APLOGNO(03396)
+    ap_assert(ngn);
+    *pr = NULL;
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, shed->c, APLOGNO(03396)
                   "h2_ngn_shed(%ld): pull task for engine %s, shutdown=%d", 
                   shed->c->id, ngn->id, want_shutdown);
     if (shed->aborted) {
@@ -274,7 +292,7 @@ apr_status_t h2_ngn_shed_pull_task(h2_ng
                       "h2_ngn_shed(%ld): pulled request %s for engine %s", 
                       shed->c->id, entry->task->id, ngn->id);
         ngn->no_live++;
-        *ptask = entry->task;
+        *pr = entry->r;
         entry->task->assigned = ngn;
         /* task will now run in ngn's own thread. Modules like lua
          * seem to require the correct thread set in the conn_rec.
@@ -328,7 +346,7 @@ void h2_ngn_shed_done_ngn(h2_ngn_shed *s
     
     if (!shed->aborted && !H2_REQ_ENTRIES_EMPTY(&ngn->entries)) {
         h2_ngn_entry *entry;
-        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, shed->c,
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, shed->c,
                       "h2_ngn_shed(%ld): exit engine %s (%s), "
                       "has still requests queued, shutdown=%d,"
                       "assigned=%ld, live=%ld, finished=%ld", 
@@ -340,15 +358,16 @@ void h2_ngn_shed_done_ngn(h2_ngn_shed *s
              entry != H2_REQ_ENTRIES_SENTINEL(&ngn->entries);
              entry = H2_NGN_ENTRY_NEXT(entry)) {
             h2_task *task = entry->task;
-            ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, shed->c,
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, shed->c,
                           "h2_ngn_shed(%ld): engine %s has queued task %s, "
                           "frozen=%d, aborting",
                           shed->c->id, ngn->id, task->id, task->frozen);
             ngn_done_task(shed, ngn, task, 0, 1);
+            task->engine = task->assigned = NULL;
         }
     }
     if (!shed->aborted && (ngn->no_assigned > 1 || ngn->no_live > 1)) {
-        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, shed->c,
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, shed->c,
                       "h2_ngn_shed(%ld): exit engine %s (%s), "
                       "assigned=%ld, live=%ld, finished=%ld", 
                       shed->c->id, ngn->id, ngn->type,
@@ -364,3 +383,9 @@ void h2_ngn_shed_done_ngn(h2_ngn_shed *s
     apr_hash_set(shed->ngns, ngn->type, APR_HASH_KEY_STRING, NULL);
     ngn->done = 1;
 }
+
+void h2_ngn_shed_destroy(h2_ngn_shed *shed)
+{
+    ap_assert(apr_hash_count(shed->ngns) == 0);
+}
+
diff -up --new-file httpd-2.4.23/modules/http2/h2_ngn_shed.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_ngn_shed.h
--- httpd-2.4.23/modules/http2/h2_ngn_shed.h	2016-05-23 12:55:29.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_ngn_shed.h	2016-10-27 18:53:58.000000000 +0200
@@ -28,8 +28,8 @@ struct h2_ngn_shed {
     
     unsigned int aborted : 1;
     
-    apr_uint32_t default_capacity;
-    apr_uint32_t req_buffer_size; /* preferred buffer size for responses */
+    int default_capacity;
+    apr_size_t req_buffer_size; /* preferred buffer size for responses */
 };
 
 const char *h2_req_engine_get_id(h2_req_engine *engine);
@@ -42,14 +42,16 @@ typedef apr_status_t h2_shed_ngn_init(h2
                                       const char *id, 
                                       const char *type,
                                       apr_pool_t *pool, 
-                                      apr_uint32_t req_buffer_size,
+                                      apr_size_t req_buffer_size,
                                       request_rec *r,
                                       h2_output_consumed **pconsumed,
                                       void **pbaton);
 
 h2_ngn_shed *h2_ngn_shed_create(apr_pool_t *pool, conn_rec *c,
-                                apr_uint32_t default_capactiy, 
-                                apr_uint32_t req_buffer_size); 
+                                int default_capactiy, 
+                                apr_size_t req_buffer_size); 
+
+void h2_ngn_shed_destroy(h2_ngn_shed *shed);
 
 void h2_ngn_shed_set_ctx(h2_ngn_shed *shed, void *user_ctx);
 void *h2_ngn_shed_get_ctx(h2_ngn_shed *shed);
@@ -58,13 +60,13 @@ h2_ngn_shed *h2_ngn_shed_get_shed(struct
 
 void h2_ngn_shed_abort(h2_ngn_shed *shed);
 
-apr_status_t h2_ngn_shed_push_task(h2_ngn_shed *shed, const char *ngn_type, 
-                                  struct h2_task *task, 
-                                  h2_shed_ngn_init *init_cb);
-
-apr_status_t h2_ngn_shed_pull_task(h2_ngn_shed *shed, h2_req_engine *pub_ngn, 
-                                   apr_uint32_t capacity, 
-                                   int want_shutdown, struct h2_task **ptask);
+apr_status_t h2_ngn_shed_push_request(h2_ngn_shed *shed, const char *ngn_type, 
+                                      request_rec *r, 
+                                      h2_shed_ngn_init *init_cb);
+
+apr_status_t h2_ngn_shed_pull_request(h2_ngn_shed *shed, h2_req_engine *pub_ngn, 
+                                      int capacity, 
+                                      int want_shutdown, request_rec **pr);
 
 apr_status_t h2_ngn_shed_done_task(h2_ngn_shed *shed, 
                                    struct h2_req_engine *ngn, 
diff -up --new-file httpd-2.4.23/modules/http2/h2_proxy_session.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_proxy_session.c
--- httpd-2.4.23/modules/http2/h2_proxy_session.c	2016-06-22 15:11:03.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_proxy_session.c	2017-05-02 17:29:13.000000000 +0200
@@ -35,17 +35,21 @@ typedef struct h2_proxy_stream {
 
     const char *url;
     request_rec *r;
-    h2_request *req;
+    h2_proxy_request *req;
+    const char *real_server_uri;
+    const char *p_server_uri;
     int standalone;
 
-    h2_stream_state_t state;
+    h2_proxy_stream_state_t state;
     unsigned int suspended : 1;
-    unsigned int data_sent : 1;
-    unsigned int data_received : 1;
+    unsigned int waiting_on_100 : 1;
+    unsigned int waiting_on_ping : 1;
     uint32_t error_code;
 
     apr_bucket_brigade *input;
+    apr_off_t data_sent;
     apr_bucket_brigade *output;
+    apr_off_t data_received;
     
     apr_table_t *saves;
 } h2_proxy_stream;
@@ -53,6 +57,9 @@ typedef struct h2_proxy_stream {
 
 static void dispatch_event(h2_proxy_session *session, h2_proxys_event_t ev, 
                            int arg, const char *msg);
+static void ping_arrived(h2_proxy_session *session);
+static apr_status_t check_suspended(h2_proxy_session *session);
+static void stream_resume(h2_proxy_stream *stream);
 
 
 static apr_status_t proxy_session_pre_close(void *theconn)
@@ -64,7 +71,7 @@ static apr_status_t proxy_session_pre_cl
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, 
                       "proxy_session(%s): pool cleanup, state=%d, streams=%d",
                       session->id, session->state, 
-                      (int)h2_ihash_count(session->streams));
+                      (int)h2_proxy_ihash_count(session->streams));
         session->aborted = 1;
         dispatch_event(session, H2_PROXYS_EV_PRE_CLOSE, 0, NULL);
         nghttp2_session_del(session->ngh2);
@@ -94,7 +101,7 @@ static int proxy_pass_brigade(apr_bucket
      * issues in case of error returned below. */
     apr_brigade_cleanup(bb);
     if (status != APR_SUCCESS) {
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, origin, APLOGNO(03357)
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, origin, APLOGNO(03357)
                       "pass output failed to %pI (%s)",
                       p_conn->addr, p_conn->hostname);
     }
@@ -131,19 +138,64 @@ static int on_frame_recv(nghttp2_session
                          void *user_data) 
 {
     h2_proxy_session *session = user_data;
+    h2_proxy_stream *stream;
+    request_rec *r;
     int n;
     
     if (APLOGcdebug(session->c)) {
         char buffer[256];
         
-        h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
+        h2_proxy_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03341)
                       "h2_proxy_session(%s): recv FRAME[%s]",
                       session->id, buffer);
     }
 
+    session->last_frame_received = apr_time_now();
     switch (frame->hd.type) {
         case NGHTTP2_HEADERS:
+            stream = nghttp2_session_get_stream_user_data(ngh2, frame->hd.stream_id);
+            if (!stream) {
+                return NGHTTP2_ERR_CALLBACK_FAILURE;
+            }
+            r = stream->r;
+            if (r->status >= 100 && r->status < 200) {
+                /* By default, we will forward all interim responses when
+                 * we are sitting on a HTTP/2 connection to the client */
+                int forward = session->h2_front;
+                switch(r->status) {
+                    case 100:
+                        if (stream->waiting_on_100) {
+                            stream->waiting_on_100 = 0;
+                            r->status_line = ap_get_status_line(r->status);
+                            forward = 1;
+                        } 
+                        break;
+                    case 103:
+                        /* workaround until we get this into http protocol base
+                         * parts. without this, unknown codes are converted to
+                         * 500... */
+                        r->status_line = "103 Early Hints";
+                        break;
+                    default:
+                        r->status_line = ap_get_status_line(r->status);
+                        break;
+                }
+                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(03487) 
+                              "h2_proxy_session(%s): got interim HEADERS, "
+                              "status=%d, will forward=%d",
+                              session->id, r->status, forward);
+                if (forward) {
+                    ap_send_interim_response(r, 1);
+                }
+            }
+            stream_resume(stream);
+            break;
+        case NGHTTP2_PING:
+            if (session->check_ping) {
+                session->check_ping = 0;
+                ping_arrived(session);
+            }
             break;
         case NGHTTP2_PUSH_PROMISE:
             break;
@@ -174,7 +226,7 @@ static int before_frame_send(nghttp2_ses
     if (APLOGcdebug(session->c)) {
         char buffer[256];
 
-        h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
+        h2_proxy_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03343)
                       "h2_proxy_session(%s): sent FRAME[%s]",
                       session->id, buffer);
@@ -188,7 +240,7 @@ static int add_header(void *table, const
     return 1;
 }
 
-static void process_proxy_header(request_rec *r, const char *n, const char *v)
+static void process_proxy_header(h2_proxy_stream *stream, const char *n, const char *v)
 {
     static const struct {
         const char *name;
@@ -201,16 +253,26 @@ static void process_proxy_header(request
         { "Set-Cookie", ap_proxy_cookie_reverse_map },
         { NULL, NULL }
     };
+    request_rec *r = stream->r;
     proxy_dir_conf *dconf;
     int i;
     
-    for (i = 0; transform_hdrs[i].name; ++i) {
-        if (!ap_cstr_casecmp(transform_hdrs[i].name, n)) {
+    dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+    if (!dconf->preserve_host) {
+        for (i = 0; transform_hdrs[i].name; ++i) {
+            if (!ap_cstr_casecmp(transform_hdrs[i].name, n)) {
+                apr_table_add(r->headers_out, n,
+                              (*transform_hdrs[i].func)(r, dconf, v));
+                return;
+            }
+        }
+        if (!ap_cstr_casecmp("Link", n)) {
             dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
             apr_table_add(r->headers_out, n,
-                          (*transform_hdrs[i].func)(r, dconf, v));
+                          h2_proxy_link_reverse_map(r, dconf, 
+                                                    stream->real_server_uri, stream->p_server_uri, v));
             return;
-       }
+        }
     }
     apr_table_add(r->headers_out, n, v);
 }
@@ -240,13 +302,13 @@ static apr_status_t h2_proxy_stream_add_
         char *hname, *hvalue;
     
         hname = apr_pstrndup(stream->pool, n, nlen);
-        h2_util_camel_case_header(hname, nlen);
+        h2_proxy_util_camel_case_header(hname, nlen);
         hvalue = apr_pstrndup(stream->pool, v, vlen);
         
         ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c, 
                       "h2_proxy_stream(%s-%d): got header %s: %s", 
                       stream->session->id, stream->id, hname, hvalue);
-        process_proxy_header(stream->r, hname, hvalue);
+        process_proxy_header(stream, hname, hvalue);
     }
     return APR_SUCCESS;
 }
@@ -284,7 +346,7 @@ static void h2_proxy_stream_end_headers_
         
         /* If USE_CANONICAL_NAME_OFF was configured for the proxy virtual host,
          * then the server name returned by ap_get_server_name() is the
-         * origin server name (which does make too much sense with Via: headers)
+         * origin server name (which doesn't make sense with Via: headers)
          * so we use the proxy vhost's name instead.
          */
         if (server_name == stream->r->hostname) {
@@ -320,9 +382,9 @@ static void h2_proxy_stream_end_headers_
     }
 }
 
-static int on_data_chunk_recv(nghttp2_session *ngh2, uint8_t flags,
-                              int32_t stream_id, const uint8_t *data,
-                              size_t len, void *user_data) 
+static int stream_response_data(nghttp2_session *ngh2, uint8_t flags,
+                                int32_t stream_id, const uint8_t *data,
+                                size_t len, void *user_data) 
 {
     h2_proxy_session *session = user_data;
     h2_proxy_stream *stream;
@@ -342,8 +404,8 @@ static int on_data_chunk_recv(nghttp2_se
         /* last chance to manipulate response headers.
          * after this, only trailers */
         h2_proxy_stream_end_headers_out(stream);
-        stream->data_received = 1;
     }
+    stream->data_received += len;
     
     b = apr_bucket_transient_create((const char*)data, len, 
                                     stream->r->connection->bucket_alloc);
@@ -353,10 +415,11 @@ static int on_data_chunk_recv(nghttp2_se
     b = apr_bucket_flush_create(stream->r->connection->bucket_alloc);
     APR_BRIGADE_INSERT_TAIL(stream->output, b);
     
-    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, stream->r, APLOGNO(03359)
-                  "h2_proxy_session(%s): pass response data for "
-                  "stream %d, %d bytes", session->id, stream_id, (int)len);
     status = ap_pass_brigade(stream->r->output_filters, stream->output);
+    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(03359)
+                  "h2_proxy_session(%s): stream=%d, response DATA %ld, %ld"
+                  " total", session->id, stream_id, (long)len,
+                  (long)stream->data_received);
     if (status != APR_SUCCESS) {
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(03344)
                       "h2_proxy_session(%s): passing output on stream %d", 
@@ -383,7 +446,7 @@ static int on_stream_close(nghttp2_sessi
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03360)
                       "h2_proxy_session(%s): stream=%d, closed, err=%d", 
                       session->id, stream_id, error_code);
-        stream = h2_ihash_get(session->streams, stream_id);
+        stream = h2_proxy_ihash_get(session->streams, stream_id);
         if (stream) {
             stream->error_code = error_code;
         }
@@ -417,10 +480,10 @@ static int on_header(nghttp2_session *ng
     return 0;
 }
 
-static ssize_t stream_data_read(nghttp2_session *ngh2, int32_t stream_id, 
-                                uint8_t *buf, size_t length,
-                                uint32_t *data_flags, 
-                                nghttp2_data_source *source, void *user_data)
+static ssize_t stream_request_data(nghttp2_session *ngh2, int32_t stream_id, 
+                                   uint8_t *buf, size_t length,
+                                   uint32_t *data_flags, 
+                                   nghttp2_data_source *source, void *user_data)
 {
     h2_proxy_stream *stream;
     apr_status_t status = APR_SUCCESS;
@@ -434,7 +497,17 @@ static ssize_t stream_data_read(nghttp2_
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
     
-    if (APR_BRIGADE_EMPTY(stream->input)) {
+    if (stream->session->check_ping) {
+        /* suspend until we hear from the other side */
+        stream->waiting_on_ping = 1;
+        status = APR_EAGAIN;
+    }
+    else if (stream->r->expecting_100) {
+        /* suspend until the answer comes */
+        stream->waiting_on_100 = 1;
+        status = APR_EAGAIN;
+    }
+    else if (APR_BRIGADE_EMPTY(stream->input)) {
         status = ap_get_brigade(stream->r->input_filters, stream->input,
                                 AP_MODE_READBYTES, APR_NONBLOCK_READ,
                                 H2MAX(APR_BUCKET_BUFF_SIZE, length));
@@ -476,10 +549,12 @@ static ssize_t stream_data_read(nghttp2_
             apr_bucket_delete(b);
         }
 
-        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, status, stream->r, 
-                      "h2_proxy_stream(%d): request body read %ld bytes, flags=%d", 
-                      stream->id, (long)readlen, (int)*data_flags);
-        stream->data_sent = 1;
+        stream->data_sent += readlen;
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, stream->r, APLOGNO(03468) 
+                      "h2_proxy_stream(%d): request DATA %ld, %ld"
+                      " total, flags=%d", 
+                      stream->id, (long)readlen, (long)stream->data_sent,
+                      (int)*data_flags);
         return readlen;
     }
     else if (APR_STATUS_IS_EAGAIN(status)) {
@@ -488,7 +563,7 @@ static ssize_t stream_data_read(nghttp2_
                       "h2_proxy_stream(%s-%d): suspending", 
                       stream->session->id, stream_id);
         stream->suspended = 1;
-        h2_iq_add(stream->session->suspended, stream->id, NULL, NULL);
+        h2_proxy_iq_add(stream->session->suspended, stream->id, NULL, NULL);
         return NGHTTP2_ERR_DEFERRED;
     }
     else {
@@ -498,8 +573,30 @@ static ssize_t stream_data_read(nghttp2_
     }
 }
 
+#ifdef H2_NG2_INVALID_HEADER_CB
+static int on_invalid_header_cb(nghttp2_session *ngh2, 
+                                const nghttp2_frame *frame, 
+                                const uint8_t *name, size_t namelen, 
+                                const uint8_t *value, size_t valuelen, 
+                                uint8_t flags, void *user_data)
+{
+    h2_proxy_session *session = user_data;
+    if (APLOGcdebug(session->c)) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03469)
+                      "h2_proxy_session(%s-%d): denying stream with invalid header "
+                      "'%s: %s'", session->id, (int)frame->hd.stream_id,
+                      apr_pstrndup(session->pool, (const char *)name, namelen),
+                      apr_pstrndup(session->pool, (const char *)value, valuelen));
+    }
+    return nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
+                                     frame->hd.stream_id, 
+                                     NGHTTP2_PROTOCOL_ERROR);
+}
+#endif
+
 h2_proxy_session *h2_proxy_session_setup(const char *id, proxy_conn_rec *p_conn,
                                          proxy_server_conf *conf,
+                                         int h2_front, 
                                          unsigned char window_bits_connection,
                                          unsigned char window_bits_stream,
                                          h2_proxy_request_done *done)
@@ -520,10 +617,11 @@ h2_proxy_session *h2_proxy_session_setup
         session->conf = conf;
         session->pool = p_conn->scpool;
         session->state = H2_PROXYS_ST_INIT;
+        session->h2_front = h2_front;
         session->window_bits_stream = window_bits_stream;
         session->window_bits_connection = window_bits_connection;
-        session->streams = h2_ihash_create(pool, offsetof(h2_proxy_stream, id));
-        session->suspended = h2_iq_create(pool, 5);
+        session->streams = h2_proxy_ihash_create(pool, offsetof(h2_proxy_stream, id));
+        session->suspended = h2_proxy_iq_create(pool, 5);
         session->done = done;
     
         session->input = apr_brigade_create(session->pool, session->c->bucket_alloc);
@@ -531,11 +629,14 @@ h2_proxy_session *h2_proxy_session_setup
     
         nghttp2_session_callbacks_new(&cbs);
         nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv);
-        nghttp2_session_callbacks_set_on_data_chunk_recv_callback(cbs, on_data_chunk_recv);
+        nghttp2_session_callbacks_set_on_data_chunk_recv_callback(cbs, stream_response_data);
         nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close);
         nghttp2_session_callbacks_set_on_header_callback(cbs, on_header);
         nghttp2_session_callbacks_set_before_frame_send_callback(cbs, before_frame_send);
         nghttp2_session_callbacks_set_send_callback(cbs, raw_send);
+#ifdef H2_NG2_INVALID_HEADER_CB
+        nghttp2_session_callbacks_set_on_invalid_header_callback(cbs, on_invalid_header_cb);
+#endif
         
         nghttp2_option_new(&option);
         nghttp2_option_set_peer_max_concurrent_streams(option, 100);
@@ -549,6 +650,14 @@ h2_proxy_session *h2_proxy_session_setup
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03362)
                       "setup session for %s", p_conn->hostname);
     }
+    else {
+        h2_proxy_session *session = p_conn->data;
+        apr_interval_time_t age = apr_time_now() - session->last_frame_received;
+        if (age > apr_time_from_sec(1)) {
+            session->check_ping = 1;
+            nghttp2_submit_ping(session->ngh2, 0, (const uint8_t *)"nevergonnagiveyouup");
+        }
+    }
     return p_conn->data;
 }
 
@@ -590,6 +699,7 @@ static apr_status_t open_stream(h2_proxy
     apr_uri_t puri;
     const char *authority, *scheme, *path;
     apr_status_t status;
+    proxy_dir_conf *dconf;
 
     stream = apr_pcalloc(r->pool, sizeof(*stream));
 
@@ -603,26 +713,64 @@ static apr_status_t open_stream(h2_proxy
     stream->input = apr_brigade_create(stream->pool, session->c->bucket_alloc);
     stream->output = apr_brigade_create(stream->pool, session->c->bucket_alloc);
     
-    stream->req = h2_req_create(1, stream->pool, 0);
+    stream->req = h2_proxy_req_create(1, stream->pool, 0);
 
     status = apr_uri_parse(stream->pool, url, &puri);
     if (status != APR_SUCCESS)
         return status;
-
+    
     scheme = (strcmp(puri.scheme, "h2")? "http" : "https");
-    authority = puri.hostname;
-    if (!ap_strchr_c(authority, ':') && puri.port
-        && apr_uri_port_of_scheme(scheme) != puri.port) {
-        /* port info missing and port is not default for scheme: append */
-        authority = apr_psprintf(stream->pool, "%s:%d", authority, puri.port);
+    
+    dconf = ap_get_module_config(r->per_dir_config, &proxy_module);
+    if (dconf->preserve_host) {
+        authority = r->hostname;
     }
+    else {
+        authority = puri.hostname;
+        if (!ap_strchr_c(authority, ':') && puri.port
+            && apr_uri_port_of_scheme(scheme) != puri.port) {
+            /* port info missing and port is not default for scheme: append */
+            authority = apr_psprintf(stream->pool, "%s:%d", authority, puri.port);
+        }
+    }
+    
+    /* we need this for mapping relative uris in headers ("Link") back
+     * to local uris */
+    stream->real_server_uri = apr_psprintf(stream->pool, "%s://%s", scheme, authority); 
+    stream->p_server_uri = apr_psprintf(stream->pool, "%s://%s", puri.scheme, authority); 
     path = apr_uri_unparse(stream->pool, &puri, APR_URI_UNP_OMITSITEPART);
-    h2_req_make(stream->req, stream->pool, r->method, scheme,
+    h2_proxy_req_make(stream->req, stream->pool, r->method, scheme,
                 authority, path, r->headers_in);
 
+    if (dconf->add_forwarded_headers) {
+        if (PROXYREQ_REVERSE == r->proxyreq) {
+            const char *buf;
+
+            /* Add X-Forwarded-For: so that the upstream has a chance to
+             * determine, where the original request came from.
+             */
+            apr_table_mergen(stream->req->headers, "X-Forwarded-For",
+                             r->useragent_ip);
+
+            /* Add X-Forwarded-Host: so that upstream knows what the
+             * original request hostname was.
+             */
+            if ((buf = apr_table_get(r->headers_in, "Host"))) {
+                apr_table_mergen(stream->req->headers, "X-Forwarded-Host", buf);
+            }
+
+            /* Add X-Forwarded-Server: so that upstream knows what the
+             * name of this proxy server is (if there are more than one)
+             * XXX: This duplicates Via: - do we strictly need it?
+             */
+            apr_table_mergen(stream->req->headers, "X-Forwarded-Server",
+                             r->server->server_hostname);
+        }
+    }
+    
     /* Tuck away all already existing cookies */
     stream->saves = apr_table_make(r->pool, 2);
-    apr_table_do(add_header, stream->saves, r->headers_out,"Set-Cookie", NULL);
+    apr_table_do(add_header, stream->saves, r->headers_out, "Set-Cookie", NULL);
 
     *pstream = stream;
     
@@ -631,40 +779,45 @@ static apr_status_t open_stream(h2_proxy
 
 static apr_status_t submit_stream(h2_proxy_session *session, h2_proxy_stream *stream)
 {
-    h2_ngheader *hd;
+    h2_proxy_ngheader *hd;
     nghttp2_data_provider *pp = NULL;
     nghttp2_data_provider provider;
-    int rv;
+    int rv, may_have_request_body = 1;
     apr_status_t status;
 
-    hd = h2_util_ngheader_make_req(stream->pool, stream->req);
+    hd = h2_proxy_util_nghd_make_req(stream->pool, stream->req);
     
-    status = ap_get_brigade(stream->r->input_filters, stream->input,
-                            AP_MODE_READBYTES, APR_NONBLOCK_READ,
-                            APR_BUCKET_BUFF_SIZE);
-    if ((status == APR_SUCCESS && !APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(stream->input)))
-        || APR_STATUS_IS_EAGAIN(status)) {
-        /* there might be data coming */
+    /* If we expect a 100-continue response, we must refrain from reading
+       any input until we get it. Reading the input will possibly trigger
+       HTTP_IN filter to generate the 100-continue itself. */
+    if (stream->waiting_on_100 || stream->waiting_on_ping) {
+        /* make a small test if we get an EOF/EOS immediately */
+        status = ap_get_brigade(stream->r->input_filters, stream->input,
+                                AP_MODE_READBYTES, APR_NONBLOCK_READ,
+                                APR_BUCKET_BUFF_SIZE);
+        may_have_request_body = APR_STATUS_IS_EAGAIN(status)
+                                || (status == APR_SUCCESS 
+                                    && !APR_BUCKET_IS_EOS(APR_BRIGADE_FIRST(stream->input)));
+    }
+    
+    if (may_have_request_body) {
         provider.source.fd = 0;
         provider.source.ptr = NULL;
-        provider.read_callback = stream_data_read;
+        provider.read_callback = stream_request_data;
         pp = &provider;
     }
 
     rv = nghttp2_submit_request(session->ngh2, NULL, 
                                 hd->nv, hd->nvlen, pp, stream);
                                 
-    if (APLOGcdebug(session->c)) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03363)
-                      "h2_proxy_session(%s): submit %s%s -> %d", 
-                      session->id, stream->req->authority, stream->req->path,
-                      rv);
-    }
-    
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03363)
+                  "h2_proxy_session(%s): submit %s%s -> %d", 
+                  session->id, stream->req->authority, stream->req->path,
+                  rv);
     if (rv > 0) {
         stream->id = rv;
         stream->state = H2_STREAM_ST_OPEN;
-        h2_ihash_add(session->streams, stream);
+        h2_proxy_ihash_add(session->streams, stream);
         dispatch_event(session, H2_PROXYS_EV_STREAM_SUBMITTED, rv, NULL);
         
         return APR_SUCCESS;
@@ -747,7 +900,7 @@ static apr_status_t h2_proxy_session_rea
                                 AP_MODE_READBYTES, 
                                 block? APR_BLOCK_READ : APR_NONBLOCK_READ, 
                                 64 * 1024);
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, 
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c, 
                       "h2_proxy_session(%s): read from conn", session->id);
         if (socket && save_timeout != -1) {
             apr_socket_timeout_set(socket, save_timeout);
@@ -788,6 +941,18 @@ apr_status_t h2_proxy_session_submit(h2_
     return status;
 }
 
+static void stream_resume(h2_proxy_stream *stream)
+{
+    h2_proxy_session *session = stream->session;
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
+                  "h2_proxy_stream(%s-%d): resuming", 
+                  session->id, stream->id);
+    stream->suspended = 0;
+    h2_proxy_iq_remove(session->suspended, stream->id);
+    nghttp2_session_resume_data(session->ngh2, stream->id);
+    dispatch_event(session, H2_PROXYS_EV_STREAM_RESUMED, 0, NULL);
+}
+
 static apr_status_t check_suspended(h2_proxy_session *session)
 {
     h2_proxy_stream *stream;
@@ -798,17 +963,16 @@ static apr_status_t check_suspended(h2_p
         stream_id = session->suspended->elts[i];
         stream = nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
         if (stream) {
-            status = ap_get_brigade(stream->r->input_filters, stream->input,
-                                    AP_MODE_READBYTES, APR_NONBLOCK_READ,
-                                    APR_BUCKET_BUFF_SIZE);
+            if (stream->waiting_on_100 || stream->waiting_on_ping) {
+                status = APR_EAGAIN;
+            }
+            else {
+                status = ap_get_brigade(stream->r->input_filters, stream->input,
+                                        AP_MODE_READBYTES, APR_NONBLOCK_READ,
+                                        APR_BUCKET_BUFF_SIZE);
+            }
             if (status == APR_SUCCESS && !APR_BRIGADE_EMPTY(stream->input)) {
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, 
-                              "h2_proxy_stream(%s-%d): resuming", 
-                              session->id, stream_id);
-                stream->suspended = 0;
-                h2_iq_remove(session->suspended, stream_id);
-                nghttp2_session_resume_data(session->ngh2, stream_id);
-                dispatch_event(session, H2_PROXYS_EV_STREAM_RESUMED, 0, NULL);
+                stream_resume(stream);
                 check_suspended(session);
                 return APR_SUCCESS;
             }
@@ -816,15 +980,14 @@ static apr_status_t check_suspended(h2_p
                 ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, session->c, 
                               APLOGNO(03382) "h2_proxy_stream(%s-%d): check input", 
                               session->id, stream_id);
-                h2_iq_remove(session->suspended, stream_id);
-                dispatch_event(session, H2_PROXYS_EV_STREAM_RESUMED, 0, NULL);
+                stream_resume(stream);
                 check_suspended(session);
                 return APR_SUCCESS;
             }
         }
         else {
             /* gone? */
-            h2_iq_remove(session->suspended, stream_id);
+            h2_proxy_iq_remove(session->suspended, stream_id);
             check_suspended(session);
             return APR_SUCCESS;
         }
@@ -838,7 +1001,7 @@ static apr_status_t session_shutdown(h2_
     apr_status_t status = APR_SUCCESS;
     const char *err = msg;
     
-    AP_DEBUG_ASSERT(session);
+    ap_assert(session);
     if (!err && reason) {
         err = nghttp2_strerror(reason);
     }
@@ -893,7 +1056,7 @@ static void ev_init(h2_proxy_session *se
 {
     switch (session->state) {
         case H2_PROXYS_ST_INIT:
-            if (h2_ihash_empty(session->streams)) {
+            if (h2_proxy_ihash_empty(session->streams)) {
                 transit(session, "init", H2_PROXYS_ST_IDLE);
             }
             else {
@@ -1000,7 +1163,7 @@ static void ev_no_io(h2_proxy_session *s
              * CPU cycles. Ideally, we'd like to do a blocking read, but that
              * is not possible if we have scheduled tasks and wait
              * for them to produce something. */
-            if (h2_ihash_empty(session->streams)) {
+            if (h2_proxy_ihash_empty(session->streams)) {
                 if (!is_accepting_streams(session)) {
                     /* We are no longer accepting new streams and have
                      * finished processing existing ones. Time to leave. */
@@ -1049,16 +1212,19 @@ static void ev_stream_done(h2_proxy_sess
     if (stream) {
         int touched = (stream->data_sent || 
                        stream_id <= session->last_stream_id);
-        int complete = (stream->error_code == 0);
+        apr_status_t status = (stream->error_code == 0)? APR_SUCCESS : APR_EINVAL;
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03364)
                       "h2_proxy_sesssion(%s): stream(%d) closed "
-                      "(complete=%d, touched=%d)", 
-                      session->id, stream_id, complete, touched);
+                      "(touched=%d, error=%d)", 
+                      session->id, stream_id, touched, stream->error_code);
         
-        if (complete && !stream->data_received) {
+        if (status != APR_SUCCESS) {
+            stream->r->status = 500;
+        }
+        else if (!stream->data_received) {
             apr_bucket *b;
             /* if the response had no body, this is the time to flush
-             * an empty brigade which will also "write" the resonse
+             * an empty brigade which will also write the resonse
              * headers */
             h2_proxy_stream_end_headers_out(stream);
             stream->data_received = 1;
@@ -1070,10 +1236,10 @@ static void ev_stream_done(h2_proxy_sess
         }
         
         stream->state = H2_STREAM_ST_CLOSED;
-        h2_ihash_remove(session->streams, stream_id);
-        h2_iq_remove(session->suspended, stream_id);
+        h2_proxy_ihash_remove(session->streams, stream_id);
+        h2_proxy_iq_remove(session->suspended, stream_id);
         if (session->done) {
-            session->done(session, stream->r, complete, touched);
+            session->done(session, stream->r, status, touched);
         }
     }
     
@@ -1185,6 +1351,21 @@ static void dispatch_event(h2_proxy_sess
     }
 }
 
+static int send_loop(h2_proxy_session *session)
+{
+    while (nghttp2_session_want_write(session->ngh2)) {
+        int rv = nghttp2_session_send(session->ngh2);
+        if (rv < 0 && nghttp2_is_fatal(rv)) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
+                          "h2_proxy_session(%s): write, rv=%d", session->id, rv);
+            dispatch_event(session, H2_PROXYS_EV_CONN_ERROR, rv, NULL);
+            break;
+        }
+        return 1;
+    }
+    return 0;
+}
+
 apr_status_t h2_proxy_session_process(h2_proxy_session *session)
 {
     apr_status_t status;
@@ -1209,16 +1390,7 @@ run_loop:
         case H2_PROXYS_ST_BUSY:
         case H2_PROXYS_ST_LOCAL_SHUTDOWN:
         case H2_PROXYS_ST_REMOTE_SHUTDOWN:
-            while (nghttp2_session_want_write(session->ngh2)) {
-                int rv = nghttp2_session_send(session->ngh2);
-                if (rv < 0 && nghttp2_is_fatal(rv)) {
-                    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
-                                  "h2_proxy_session(%s): write, rv=%d", session->id, rv);
-                    dispatch_event(session, H2_PROXYS_EV_CONN_ERROR, rv, NULL);
-                    break;
-                }
-                have_written = 1;
-            }
+            have_written = send_loop(session);
             
             if (nghttp2_session_want_read(session->ngh2)) {
                 status = h2_proxy_session_read(session, 0, 0);
@@ -1247,7 +1419,7 @@ run_loop:
                 }
                 
                 status = h2_proxy_session_read(session, 1, session->wait_timeout);
-                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, 
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c, 
                               APLOGNO(03365)
                               "h2_proxy_session(%s): WAIT read, timeout=%fms", 
                               session->id, (float)session->wait_timeout/1000.0);
@@ -1295,28 +1467,71 @@ typedef struct {
     h2_proxy_request_done *done;
 } cleanup_iter_ctx;
 
+static int cancel_iter(void *udata, void *val)
+{
+    cleanup_iter_ctx *ctx = udata;
+    h2_proxy_stream *stream = val;
+    nghttp2_submit_rst_stream(ctx->session->ngh2, NGHTTP2_FLAG_NONE,
+                              stream->id, 0);
+    return 1;
+}
+
+void h2_proxy_session_cancel_all(h2_proxy_session *session)
+{
+    if (!h2_proxy_ihash_empty(session->streams)) {
+        cleanup_iter_ctx ctx;
+        ctx.session = session;
+        ctx.done = session->done;
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03366)
+                      "h2_proxy_session(%s): cancel  %d streams",
+                      session->id, (int)h2_proxy_ihash_count(session->streams));
+        h2_proxy_ihash_iter(session->streams, cancel_iter, &ctx);
+        session_shutdown(session, 0, NULL);
+    }
+}
+
 static int done_iter(void *udata, void *val)
 {
     cleanup_iter_ctx *ctx = udata;
     h2_proxy_stream *stream = val;
-    int touched = (!ctx->session->last_stream_id || 
+    int touched = (stream->data_sent || 
                    stream->id <= ctx->session->last_stream_id);
-    ctx->done(ctx->session, stream->r, 0, touched);
+    ctx->done(ctx->session, stream->r, APR_ECONNABORTED, touched);
     return 1;
 }
 
 void h2_proxy_session_cleanup(h2_proxy_session *session, 
                               h2_proxy_request_done *done)
 {
-    if (session->streams && !h2_ihash_empty(session->streams)) {
+    if (!h2_proxy_ihash_empty(session->streams)) {
         cleanup_iter_ctx ctx;
         ctx.session = session;
         ctx.done = done;
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03366)
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03519)
                       "h2_proxy_session(%s): terminated, %d streams unfinished",
-                      session->id, (int)h2_ihash_count(session->streams));
-        h2_ihash_iter(session->streams, done_iter, &ctx);
-        h2_ihash_clear(session->streams);
+                      session->id, (int)h2_proxy_ihash_count(session->streams));
+        h2_proxy_ihash_iter(session->streams, done_iter, &ctx);
+        h2_proxy_ihash_clear(session->streams);
+    }
+}
+
+static int ping_arrived_iter(void *udata, void *val)
+{
+    h2_proxy_stream *stream = val;
+    if (stream->waiting_on_ping) {
+        stream->waiting_on_ping = 0;
+        stream_resume(stream);
+    }
+    return 1;
+}
+
+static void ping_arrived(h2_proxy_session *session)
+{
+    if (!h2_proxy_ihash_empty(session->streams)) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03470)
+                      "h2_proxy_session(%s): ping arrived, unblocking streams",
+                      session->id);
+        h2_proxy_ihash_iter(session->streams, ping_arrived_iter, &session);
     }
 }
 
@@ -1347,13 +1562,13 @@ static int win_update_iter(void *udata,
 void h2_proxy_session_update_window(h2_proxy_session *session, 
                                     conn_rec *c, apr_off_t bytes)
 {
-    if (session->streams && !h2_ihash_empty(session->streams)) {
+    if (!h2_proxy_ihash_empty(session->streams)) {
         win_update_ctx ctx;
         ctx.session = session;
         ctx.c = c;
         ctx.bytes = bytes;
         ctx.updated = 0;
-        h2_ihash_iter(session->streams, win_update_iter, &ctx);
+        h2_proxy_ihash_iter(session->streams, win_update_iter, &ctx);
         
         if (!ctx.updated) {
             /* could not find the stream any more, possibly closed, update
diff -up --new-file httpd-2.4.23/modules/http2/h2_proxy_session.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_proxy_session.h
--- httpd-2.4.23/modules/http2/h2_proxy_session.h	2016-06-13 11:58:07.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_proxy_session.h	2017-02-14 16:53:50.000000000 +0100
@@ -20,8 +20,18 @@
 
 #include <nghttp2/nghttp2.h>
 
-struct h2_iqueue;
-struct h2_ihash_t;
+struct h2_proxy_iqueue;
+struct h2_proxy_ihash_t;
+
+typedef enum {
+    H2_STREAM_ST_IDLE,
+    H2_STREAM_ST_OPEN,
+    H2_STREAM_ST_RESV_LOCAL,
+    H2_STREAM_ST_RESV_REMOTE,
+    H2_STREAM_ST_CLOSED_INPUT,
+    H2_STREAM_ST_CLOSED_OUTPUT,
+    H2_STREAM_ST_CLOSED,
+} h2_proxy_stream_state_t;
 
 typedef enum {
     H2_PROXYS_ST_INIT,             /* send initial SETTINGS, etc. */
@@ -52,7 +62,7 @@ typedef enum {
 
 typedef struct h2_proxy_session h2_proxy_session;
 typedef void h2_proxy_request_done(h2_proxy_session *s, request_rec *r,
-                                   int complete, int touched);
+                                   apr_status_t status, int touched);
 
 struct h2_proxy_session {
     const char *id;
@@ -63,6 +73,8 @@ struct h2_proxy_session {
     nghttp2_session *ngh2;   /* the nghttp2 session itself */
     
     unsigned int aborted : 1;
+    unsigned int check_ping : 1;
+    unsigned int h2_front : 1; /* if front-end connection is HTTP/2 */
 
     h2_proxy_request_done *done;
     void *user_data;
@@ -73,10 +85,11 @@ struct h2_proxy_session {
     h2_proxys_state state;
     apr_interval_time_t wait_timeout;
 
-    struct h2_ihash_t *streams;
-    struct h2_iqueue *suspended;
+    struct h2_proxy_ihash_t *streams;
+    struct h2_proxy_iqueue *suspended;
     apr_size_t remote_max_concurrent;
     int last_stream_id;     /* last stream id processed by backend, or 0 */
+    apr_time_t last_frame_received;
     
     apr_bucket_brigade *input;
     apr_bucket_brigade *output;
@@ -84,6 +97,7 @@ struct h2_proxy_session {
 
 h2_proxy_session *h2_proxy_session_setup(const char *id, proxy_conn_rec *p_conn,
                                          proxy_server_conf *conf,
+                                         int h2_front, 
                                          unsigned char window_bits_connection,
                                          unsigned char window_bits_stream,
                                          h2_proxy_request_done *done);
@@ -101,6 +115,8 @@ apr_status_t h2_proxy_session_submit(h2_
  */
 apr_status_t h2_proxy_session_process(h2_proxy_session *s);
 
+void h2_proxy_session_cancel_all(h2_proxy_session *s);
+
 void h2_proxy_session_cleanup(h2_proxy_session *s, h2_proxy_request_done *done);
 
 void h2_proxy_session_update_window(h2_proxy_session *s, 
diff -up --new-file httpd-2.4.23/modules/http2/h2_proxy_util.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_proxy_util.c
--- httpd-2.4.23/modules/http2/h2_proxy_util.c	2016-06-28 21:57:30.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_proxy_util.c	2017-04-10 17:04:55.000000000 +0200
@@ -14,20 +14,26 @@
  */
 
 #include <assert.h>
+#include <apr_lib.h>
 #include <apr_strings.h>
+#include <apr_thread_mutex.h>
+#include <apr_thread_cond.h>
 
 #include <httpd.h>
 #include <http_core.h>
 #include <http_log.h>
 #include <http_request.h>
+#include <mod_proxy.h>
 
 #include <nghttp2/nghttp2.h>
 
 #include "h2.h"
 #include "h2_proxy_util.h"
 
+APLOG_USE_MODULE(proxy_http2);
+
 /* h2_log2(n) iff n is a power of 2 */
-unsigned char h2_log2(apr_uint32_t n)
+unsigned char h2_proxy_log2(int n)
 {
     int lz = 0;
     if (!n) {
@@ -59,7 +65,7 @@ unsigned char h2_log2(apr_uint32_t n)
 /*******************************************************************************
  * ihash - hash for structs with int identifier
  ******************************************************************************/
-struct h2_ihash_t {
+struct h2_proxy_ihash_t {
     apr_hash_t *hash;
     size_t ioff;
 };
@@ -69,31 +75,31 @@ static unsigned int ihash(const char *ke
     return (unsigned int)(*((int*)key));
 }
 
-h2_ihash_t *h2_ihash_create(apr_pool_t *pool, size_t offset_of_int)
+h2_proxy_ihash_t *h2_proxy_ihash_create(apr_pool_t *pool, size_t offset_of_int)
 {
-    h2_ihash_t *ih = apr_pcalloc(pool, sizeof(h2_ihash_t));
+    h2_proxy_ihash_t *ih = apr_pcalloc(pool, sizeof(h2_proxy_ihash_t));
     ih->hash = apr_hash_make_custom(pool, ihash);
     ih->ioff = offset_of_int;
     return ih;
 }
 
-size_t h2_ihash_count(h2_ihash_t *ih)
+size_t h2_proxy_ihash_count(h2_proxy_ihash_t *ih)
 {
     return apr_hash_count(ih->hash);
 }
 
-int h2_ihash_empty(h2_ihash_t *ih)
+int h2_proxy_ihash_empty(h2_proxy_ihash_t *ih)
 {
     return apr_hash_count(ih->hash) == 0;
 }
 
-void *h2_ihash_get(h2_ihash_t *ih, int id)
+void *h2_proxy_ihash_get(h2_proxy_ihash_t *ih, int id)
 {
     return apr_hash_get(ih->hash, &id, sizeof(id));
 }
 
 typedef struct {
-    h2_ihash_iter_t *iter;
+    h2_proxy_ihash_iter_t *iter;
     void *ctx;
 } iter_ctx;
 
@@ -104,7 +110,7 @@ static int ihash_iter(void *ctx, const v
     return ictx->iter(ictx->ctx, (void*)val); /* why is this passed const?*/
 }
 
-int h2_ihash_iter(h2_ihash_t *ih, h2_ihash_iter_t *fn, void *ctx)
+int h2_proxy_ihash_iter(h2_proxy_ihash_t *ih, h2_proxy_ihash_iter_t *fn, void *ctx)
 {
     iter_ctx ictx;
     ictx.iter = fn;
@@ -112,30 +118,30 @@ int h2_ihash_iter(h2_ihash_t *ih, h2_iha
     return apr_hash_do(ihash_iter, &ictx, ih->hash);
 }
 
-void h2_ihash_add(h2_ihash_t *ih, void *val)
+void h2_proxy_ihash_add(h2_proxy_ihash_t *ih, void *val)
 {
     apr_hash_set(ih->hash, ((char *)val + ih->ioff), sizeof(int), val);
 }
 
-void h2_ihash_remove(h2_ihash_t *ih, int id)
+void h2_proxy_ihash_remove(h2_proxy_ihash_t *ih, int id)
 {
     apr_hash_set(ih->hash, &id, sizeof(id), NULL);
 }
 
-void h2_ihash_remove_val(h2_ihash_t *ih, void *val)
+void h2_proxy_ihash_remove_val(h2_proxy_ihash_t *ih, void *val)
 {
     int id = *((int*)((char *)val + ih->ioff));
     apr_hash_set(ih->hash, &id, sizeof(id), NULL);
 }
 
 
-void h2_ihash_clear(h2_ihash_t *ih)
+void h2_proxy_ihash_clear(h2_proxy_ihash_t *ih)
 {
     apr_hash_clear(ih->hash);
 }
 
 typedef struct {
-    h2_ihash_t *ih;
+    h2_proxy_ihash_t *ih;
     void **buffer;
     size_t max;
     size_t len;
@@ -151,7 +157,7 @@ static int collect_iter(void *x, void *v
     return 0;
 }
 
-size_t h2_ihash_shift(h2_ihash_t *ih, void **buffer, size_t max)
+size_t h2_proxy_ihash_shift(h2_proxy_ihash_t *ih, void **buffer, size_t max)
 {
     collect_ctx ctx;
     size_t i;
@@ -160,15 +166,15 @@ size_t h2_ihash_shift(h2_ihash_t *ih, vo
     ctx.buffer = buffer;
     ctx.max = max;
     ctx.len = 0;
-    h2_ihash_iter(ih, collect_iter, &ctx);
+    h2_proxy_ihash_iter(ih, collect_iter, &ctx);
     for (i = 0; i < ctx.len; ++i) {
-        h2_ihash_remove_val(ih, buffer[i]);
+        h2_proxy_ihash_remove_val(ih, buffer[i]);
     }
     return ctx.len;
 }
 
 typedef struct {
-    h2_ihash_t *ih;
+    h2_proxy_ihash_t *ih;
     int *buffer;
     size_t max;
     size_t len;
@@ -184,7 +190,7 @@ static int icollect_iter(void *x, void *
     return 0;
 }
 
-size_t h2_ihash_ishift(h2_ihash_t *ih, int *buffer, size_t max)
+size_t h2_proxy_ihash_ishift(h2_proxy_ihash_t *ih, int *buffer, size_t max)
 {
     icollect_ctx ctx;
     size_t i;
@@ -193,9 +199,9 @@ size_t h2_ihash_ishift(h2_ihash_t *ih, i
     ctx.buffer = buffer;
     ctx.max = max;
     ctx.len = 0;
-    h2_ihash_iter(ih, icollect_iter, &ctx);
+    h2_proxy_ihash_iter(ih, icollect_iter, &ctx);
     for (i = 0; i < ctx.len; ++i) {
-        h2_ihash_remove(ih, buffer[i]);
+        h2_proxy_ihash_remove(ih, buffer[i]);
     }
     return ctx.len;
 }
@@ -204,16 +210,16 @@ size_t h2_ihash_ishift(h2_ihash_t *ih, i
  * iqueue - sorted list of int
  ******************************************************************************/
 
-static void iq_grow(h2_iqueue *q, int nlen);
-static void iq_swap(h2_iqueue *q, int i, int j);
-static int iq_bubble_up(h2_iqueue *q, int i, int top, 
-                        h2_iq_cmp *cmp, void *ctx);
-static int iq_bubble_down(h2_iqueue *q, int i, int bottom, 
-                          h2_iq_cmp *cmp, void *ctx);
+static void iq_grow(h2_proxy_iqueue *q, int nlen);
+static void iq_swap(h2_proxy_iqueue *q, int i, int j);
+static int iq_bubble_up(h2_proxy_iqueue *q, int i, int top, 
+                        h2_proxy_iq_cmp *cmp, void *ctx);
+static int iq_bubble_down(h2_proxy_iqueue *q, int i, int bottom, 
+                          h2_proxy_iq_cmp *cmp, void *ctx);
 
-h2_iqueue *h2_iq_create(apr_pool_t *pool, int capacity)
+h2_proxy_iqueue *h2_proxy_iq_create(apr_pool_t *pool, int capacity)
 {
-    h2_iqueue *q = apr_pcalloc(pool, sizeof(h2_iqueue));
+    h2_proxy_iqueue *q = apr_pcalloc(pool, sizeof(h2_proxy_iqueue));
     if (q) {
         q->pool = pool;
         iq_grow(q, capacity);
@@ -222,18 +228,18 @@ h2_iqueue *h2_iq_create(apr_pool_t *pool
     return q;
 }
 
-int h2_iq_empty(h2_iqueue *q)
+int h2_proxy_iq_empty(h2_proxy_iqueue *q)
 {
     return q->nelts == 0;
 }
 
-int h2_iq_count(h2_iqueue *q)
+int h2_proxy_iq_count(h2_proxy_iqueue *q)
 {
     return q->nelts;
 }
 
 
-void h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx)
+void h2_proxy_iq_add(h2_proxy_iqueue *q, int sid, h2_proxy_iq_cmp *cmp, void *ctx)
 {
     int i;
     
@@ -251,7 +257,7 @@ void h2_iq_add(h2_iqueue *q, int sid, h2
     }
 }
 
-int h2_iq_remove(h2_iqueue *q, int sid)
+int h2_proxy_iq_remove(h2_proxy_iqueue *q, int sid)
 {
     int i;
     for (i = 0; i < q->nelts; ++i) {
@@ -271,15 +277,15 @@ int h2_iq_remove(h2_iqueue *q, int sid)
     return 0;
 }
 
-void h2_iq_clear(h2_iqueue *q)
+void h2_proxy_iq_clear(h2_proxy_iqueue *q)
 {
     q->nelts = 0;
 }
 
-void h2_iq_sort(h2_iqueue *q, h2_iq_cmp *cmp, void *ctx)
+void h2_proxy_iq_sort(h2_proxy_iqueue *q, h2_proxy_iq_cmp *cmp, void *ctx)
 {
     /* Assume that changes in ordering are minimal. This needs,
-     * best case, q->nelts - 1 comparisions to check that nothing
+     * best case, q->nelts - 1 comparisons to check that nothing
      * changed.
      */
     if (q->nelts > 0) {
@@ -304,7 +310,7 @@ void h2_iq_sort(h2_iqueue *q, h2_iq_cmp
 }
 
 
-int h2_iq_shift(h2_iqueue *q)
+int h2_proxy_iq_shift(h2_proxy_iqueue *q)
 {
     int sid;
     
@@ -319,7 +325,7 @@ int h2_iq_shift(h2_iqueue *q)
     return sid;
 }
 
-static void iq_grow(h2_iqueue *q, int nlen)
+static void iq_grow(h2_proxy_iqueue *q, int nlen)
 {
     if (nlen > q->nalloc) {
         int *nq = apr_pcalloc(q->pool, sizeof(int) * nlen);
@@ -339,15 +345,15 @@ static void iq_grow(h2_iqueue *q, int nl
     }
 }
 
-static void iq_swap(h2_iqueue *q, int i, int j)
+static void iq_swap(h2_proxy_iqueue *q, int i, int j)
 {
     int x = q->elts[i];
     q->elts[i] = q->elts[j];
     q->elts[j] = x;
 }
 
-static int iq_bubble_up(h2_iqueue *q, int i, int top, 
-                        h2_iq_cmp *cmp, void *ctx) 
+static int iq_bubble_up(h2_proxy_iqueue *q, int i, int top, 
+                        h2_proxy_iq_cmp *cmp, void *ctx) 
 {
     int prev;
     while (((prev = (q->nalloc + i - 1) % q->nalloc), i != top) 
@@ -358,8 +364,8 @@ static int iq_bubble_up(h2_iqueue *q, in
     return i;
 }
 
-static int iq_bubble_down(h2_iqueue *q, int i, int bottom, 
-                          h2_iq_cmp *cmp, void *ctx)
+static int iq_bubble_down(h2_proxy_iqueue *q, int i, int bottom, 
+                          h2_proxy_iq_cmp *cmp, void *ctx)
 {
     int next;
     while (((next = (q->nalloc + i + 1) % q->nalloc), i != bottom) 
@@ -371,7 +377,7 @@ static int iq_bubble_down(h2_iqueue *q,
 }
 
 /*******************************************************************************
- * h2_ngheader
+ * h2_proxy_ngheader
  ******************************************************************************/
 #define H2_HD_MATCH_LIT_CS(l, name)  \
     ((strlen(name) == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
@@ -397,7 +403,7 @@ static int count_header(void *ctx, const
 #define NV_ADD_LIT_CS(nv, k, v)     add_header(nv, k, sizeof(k) - 1, v, strlen(v))
 #define NV_ADD_CS_CS(nv, k, v)      add_header(nv, k, strlen(k), v, strlen(v))
 
-static int add_header(h2_ngheader *ngh, 
+static int add_header(h2_proxy_ngheader *ngh, 
                       const char *key, size_t key_len,
                       const char *value, size_t val_len)
 {
@@ -418,23 +424,23 @@ static int add_table_header(void *ctx, c
     return 1;
 }
 
-h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, 
-                                       const struct h2_request *req)
+h2_proxy_ngheader *h2_proxy_util_nghd_make_req(apr_pool_t *p, 
+                                               const h2_proxy_request *req)
 {
     
-    h2_ngheader *ngh;
+    h2_proxy_ngheader *ngh;
     size_t n;
     
-    AP_DEBUG_ASSERT(req);
-    AP_DEBUG_ASSERT(req->scheme);
-    AP_DEBUG_ASSERT(req->authority);
-    AP_DEBUG_ASSERT(req->path);
-    AP_DEBUG_ASSERT(req->method);
+    ap_assert(req);
+    ap_assert(req->scheme);
+    ap_assert(req->authority);
+    ap_assert(req->path);
+    ap_assert(req->method);
 
     n = 4;
     apr_table_do(count_header, &n, req->headers, NULL);
     
-    ngh = apr_pcalloc(p, sizeof(h2_ngheader));
+    ngh = apr_pcalloc(p, sizeof(h2_proxy_ngheader));
     ngh->nv =  apr_pcalloc(p, n * sizeof(nghttp2_nv));
     NV_ADD_LIT_CS(ngh, ":scheme", req->scheme);
     NV_ADD_LIT_CS(ngh, ":authority", req->authority);
@@ -458,7 +464,6 @@ typedef struct {
 #define H2_LIT_ARGS(a)      (a),H2_ALEN(a)
 
 static literal IgnoredRequestHeaders[] = {
-    H2_DEF_LITERAL("expect"),
     H2_DEF_LITERAL("upgrade"),
     H2_DEF_LITERAL("connection"),
     H2_DEF_LITERAL("keep-alive"),
@@ -485,18 +490,18 @@ static int ignore_header(const literal *
     return 0;
 }
 
-static int h2_req_ignore_header(const char *name, size_t len)
+static int h2_proxy_req_ignore_header(const char *name, size_t len)
 {
     return ignore_header(H2_LIT_ARGS(IgnoredRequestHeaders), name, len);
 }
 
 int h2_proxy_res_ignore_header(const char *name, size_t len)
 {
-    return (h2_req_ignore_header(name, len) 
+    return (h2_proxy_req_ignore_header(name, len) 
             || ignore_header(H2_LIT_ARGS(IgnoredProxyRespHds), name, len));
 }
 
-void h2_util_camel_case_header(char *s, size_t len)
+void h2_proxy_util_camel_case_header(char *s, size_t len)
 {
     size_t start = 1;
     size_t i;
@@ -528,7 +533,7 @@ static apr_status_t h2_headers_add_h1(ap
 {
     char *hname, *hvalue;
     
-    if (h2_req_ignore_header(name, nlen)) {
+    if (h2_proxy_req_ignore_header(name, nlen)) {
         return APR_SUCCESS;
     }
     else if (H2_HD_MATCH_LIT("cookie", name, nlen)) {
@@ -553,20 +558,19 @@ static apr_status_t h2_headers_add_h1(ap
     
     hname = apr_pstrndup(pool, name, nlen);
     hvalue = apr_pstrndup(pool, value, vlen);
-    h2_util_camel_case_header(hname, nlen);
+    h2_proxy_util_camel_case_header(hname, nlen);
     apr_table_mergen(headers, hname, hvalue);
     
     return APR_SUCCESS;
 }
 
-static h2_request *h2_req_createn(int id, apr_pool_t *pool, const char *method, 
+static h2_proxy_request *h2_proxy_req_createn(int id, apr_pool_t *pool, const char *method, 
                                   const char *scheme, const char *authority, 
                                   const char *path, apr_table_t *header, 
                                   int serialize)
 {
-    h2_request *req = apr_pcalloc(pool, sizeof(h2_request));
+    h2_proxy_request *req = apr_pcalloc(pool, sizeof(h2_proxy_request));
     
-    req->id             = id;
     req->method         = method;
     req->scheme         = scheme;
     req->authority      = authority;
@@ -578,9 +582,9 @@ static h2_request *h2_req_createn(int id
     return req;
 }
 
-h2_request *h2_req_create(int id, apr_pool_t *pool, int serialize)
+h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool, int serialize)
 {
-    return h2_req_createn(id, pool, NULL, NULL, NULL, NULL, NULL, serialize);
+    return h2_proxy_req_createn(id, pool, NULL, NULL, NULL, NULL, NULL, serialize);
 }
 
 typedef struct {
@@ -592,13 +596,13 @@ static int set_h1_header(void *ctx, cons
 {
     h1_ctx *x = ctx;
     size_t klen = strlen(key);
-    if (!h2_req_ignore_header(key, klen)) {
+    if (!h2_proxy_req_ignore_header(key, klen)) {
         h2_headers_add_h1(x->headers, x->pool, key, klen, value, strlen(value));
     }
     return 1;
 }
 
-apr_status_t h2_req_make(h2_request *req, apr_pool_t *pool,
+apr_status_t h2_proxy_req_make(h2_proxy_request *req, apr_pool_t *pool,
                          const char *method, const char *scheme, 
                          const char *authority, const char *path, 
                          apr_table_t *headers)
@@ -610,10 +614,10 @@ apr_status_t h2_req_make(h2_request *req
     req->authority = authority;
     req->path      = path;
 
-    AP_DEBUG_ASSERT(req->scheme);
-    AP_DEBUG_ASSERT(req->authority);
-    AP_DEBUG_ASSERT(req->path);
-    AP_DEBUG_ASSERT(req->method);
+    ap_assert(req->scheme);
+    ap_assert(req->authority);
+    ap_assert(req->path);
+    ap_assert(req->method);
 
     x.pool = pool;
     x.headers = req->headers;
@@ -625,7 +629,7 @@ apr_status_t h2_req_make(h2_request *req
  * frame logging
  ******************************************************************************/
 
-int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
+int h2_proxy_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen)
 {
     char scratch[128];
     size_t s_len = sizeof(scratch)/sizeof(scratch[0]);
@@ -703,3 +707,630 @@ int h2_util_frame_print(const nghttp2_fr
                                 frame->hd.flags, frame->hd.stream_id);
     }
 }
+
+/*******************************************************************************
+ * link header handling 
+ ******************************************************************************/
+
+typedef struct {
+    apr_pool_t *pool;
+    request_rec *r;
+    proxy_dir_conf *conf;
+    const char *s;
+    int slen;
+    int i;
+    const char *server_uri;
+    int su_len;
+    const char *real_backend_uri;
+    int rbu_len;
+    const char *p_server_uri;
+    int psu_len;
+    int link_start;
+    int link_end;
+} link_ctx;
+
+static int attr_char(char c) 
+{
+    switch (c) {
+        case '!':
+        case '#':
+        case '$':
+        case '&':
+        case '+':
+        case '-':
+        case '.':
+        case '^':
+        case '_':
+        case '`':
+        case '|':
+        case '~':
+            return 1;
+        default:
+            return apr_isalnum(c);
+    }
+}
+
+static int ptoken_char(char c) 
+{
+    switch (c) {
+        case '!':
+        case '#':
+        case '$':
+        case '&':
+        case '\'':
+        case '(':
+        case ')':
+        case '*':
+        case '+':
+        case '-':
+        case '.':
+        case '/':
+        case ':':
+        case '<':
+        case '=':
+        case '>':
+        case '?':
+        case '@':
+        case '[':
+        case ']':
+        case '^':
+        case '_':
+        case '`':
+        case '{':
+        case '|':
+        case '}':
+        case '~':
+            return 1;
+        default:
+            return apr_isalnum(c);
+    }
+}
+
+static int skip_ws(link_ctx *ctx)
+{
+    char c;
+    while (ctx->i < ctx->slen 
+           && (((c = ctx->s[ctx->i]) == ' ') || (c == '\t'))) {
+        ++ctx->i;
+    }
+    return (ctx->i < ctx->slen);
+}
+
+static int find_chr(link_ctx *ctx, char c, int *pidx)
+{
+    int j;
+    for (j = ctx->i; j < ctx->slen; ++j) {
+        if (ctx->s[j] == c) {
+            *pidx = j;
+            return 1;
+        }
+    } 
+    return 0;
+}
+
+static int read_chr(link_ctx *ctx, char c)
+{
+    if (ctx->i < ctx->slen && ctx->s[ctx->i] == c) {
+        ++ctx->i;
+        return 1;
+    }
+    return 0;
+}
+
+static int skip_qstring(link_ctx *ctx)
+{
+    if (skip_ws(ctx) && read_chr(ctx, '\"')) {
+        int end;
+        if (find_chr(ctx, '\"', &end)) {
+            ctx->i = end + 1;
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int skip_ptoken(link_ctx *ctx)
+{
+    if (skip_ws(ctx)) {
+        int i;
+        for (i = ctx->i; i < ctx->slen && ptoken_char(ctx->s[i]); ++i) {
+            /* nop */
+        }
+        if (i > ctx->i) {
+            ctx->i = i;
+            return 1;
+        }
+    }
+    return 0;
+}
+
+
+static int read_link(link_ctx *ctx)
+{
+    ctx->link_start = ctx->link_end = 0;
+    if (skip_ws(ctx) && read_chr(ctx, '<')) {
+        int end;
+        if (find_chr(ctx, '>', &end)) {
+            ctx->link_start = ctx->i;
+            ctx->link_end = end;
+            ctx->i = end + 1;
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int skip_pname(link_ctx *ctx)
+{
+    if (skip_ws(ctx)) {
+        int i;
+        for (i = ctx->i; i < ctx->slen && attr_char(ctx->s[i]); ++i) {
+            /* nop */
+        }
+        if (i > ctx->i) {
+            ctx->i = i;
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int skip_pvalue(link_ctx *ctx)
+{
+    if (skip_ws(ctx) && read_chr(ctx, '=')) {
+        if (skip_qstring(ctx) || skip_ptoken(ctx)) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int skip_param(link_ctx *ctx)
+{
+    if (skip_ws(ctx) && read_chr(ctx, ';')) {
+        if (skip_pname(ctx)) {
+            skip_pvalue(ctx); /* value is optional */
+            return 1;
+        }
+    }
+    return 0;
+}
+
+static int read_sep(link_ctx *ctx)
+{
+    if (skip_ws(ctx) && read_chr(ctx, ',')) {
+        return 1;
+    }
+    return 0;
+}
+
+static size_t subst_str(link_ctx *ctx, int start, int end, const char *ns)
+{
+    int olen, nlen, plen;
+    int delta;
+    char *p;
+    
+    olen = end - start;
+    nlen = (int)strlen(ns);
+    delta = nlen - olen;
+    plen = ctx->slen + delta + 1;
+    p = apr_pcalloc(ctx->pool, plen);
+    strncpy(p, ctx->s, start);
+    strncpy(p + start, ns, nlen);
+    strcpy(p + start + nlen, ctx->s + end);
+    ctx->s = p;
+    ctx->slen = (int)strlen(p);
+    if (ctx->i >= end) {
+        ctx->i += delta;
+    }
+    return nlen; 
+}
+
+static void map_link(link_ctx *ctx) 
+{
+    if (ctx->link_start < ctx->link_end) {
+        char buffer[HUGE_STRING_LEN];
+        int need_len, link_len, buffer_len, prepend_p_server; 
+        const char *mapped;
+        
+        buffer[0] = '\0';
+        buffer_len = 0;
+        link_len = ctx->link_end - ctx->link_start;
+        need_len = link_len + 1;
+        prepend_p_server = (ctx->s[ctx->link_start] == '/'); 
+        if (prepend_p_server) {
+            /* common to use relative uris in link header, for mappings
+             * to work need to prefix the backend server uri */
+            need_len += ctx->psu_len;
+            strncpy(buffer, ctx->p_server_uri, sizeof(buffer));
+            buffer_len = ctx->psu_len;
+        }
+        if (need_len > sizeof(buffer)) {
+            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, ctx->r, APLOGNO(03482) 
+                          "link_reverse_map uri too long, skipped: %s", ctx->s);
+            return;
+        }
+        strncpy(buffer + buffer_len, ctx->s + ctx->link_start, link_len);
+        buffer_len += link_len;
+        buffer[buffer_len] = '\0';
+        if (!prepend_p_server
+            && strcmp(ctx->real_backend_uri, ctx->p_server_uri)
+            && !strncmp(buffer, ctx->real_backend_uri, ctx->rbu_len)) {
+            /* the server uri and our local proxy uri we use differ, for mapping
+             * to work, we need to use the proxy uri */
+            int path_start = ctx->link_start + ctx->rbu_len;
+            link_len -= ctx->rbu_len;
+            strcpy(buffer, ctx->p_server_uri);
+            strncpy(buffer + ctx->psu_len, ctx->s + path_start, link_len);
+            buffer_len = ctx->psu_len + link_len;
+            buffer[buffer_len] = '\0';            
+        }
+        mapped = ap_proxy_location_reverse_map(ctx->r, ctx->conf, buffer);
+        ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, ctx->r, 
+                      "reverse_map[%s] %s --> %s", ctx->p_server_uri, buffer, mapped);
+        if (mapped != buffer) {
+            if (prepend_p_server) {
+                if (ctx->server_uri == NULL) {
+                    ctx->server_uri = ap_construct_url(ctx->pool, "", ctx->r);
+                    ctx->su_len = (int)strlen(ctx->server_uri);
+                }
+                if (!strncmp(mapped, ctx->server_uri, ctx->su_len)) {
+                    mapped += ctx->su_len;
+                }
+            }
+            subst_str(ctx, ctx->link_start, ctx->link_end, mapped);
+        }
+    }
+}
+
+/* RFC 5988 <https://tools.ietf.org/html/rfc5988#section-6.2.1>
+  Link           = "Link" ":" #link-value
+  link-value     = "<" URI-Reference ">" *( ";" link-param )
+  link-param     = ( ( "rel" "=" relation-types )
+                 | ( "anchor" "=" <"> URI-Reference <"> )
+                 | ( "rev" "=" relation-types )
+                 | ( "hreflang" "=" Language-Tag )
+                 | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) )
+                 | ( "title" "=" quoted-string )
+                 | ( "title*" "=" ext-value )
+                 | ( "type" "=" ( media-type | quoted-mt ) )
+                 | ( link-extension ) )
+  link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] )
+                 | ( ext-name-star "=" ext-value )
+  ext-name-star  = parmname "*" ; reserved for RFC2231-profiled
+                                ; extensions.  Whitespace NOT
+                                ; allowed in between.
+  ptoken         = 1*ptokenchar
+  ptokenchar     = "!" | "#" | "$" | "%" | "&" | "'" | "("
+                 | ")" | "*" | "+" | "-" | "." | "/" | DIGIT
+                 | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA
+                 | "[" | "]" | "^" | "_" | "`" | "{" | "|"
+                 | "}" | "~"
+  media-type     = type-name "/" subtype-name
+  quoted-mt      = <"> media-type <">
+  relation-types = relation-type
+                 | <"> relation-type *( 1*SP relation-type ) <">
+  relation-type  = reg-rel-type | ext-rel-type
+  reg-rel-type   = LOALPHA *( LOALPHA | DIGIT | "." | "-" )
+  ext-rel-type   = URI
+  
+  and from <https://tools.ietf.org/html/rfc5987>
+  parmname      = 1*attr-char
+  attr-char     = ALPHA / DIGIT
+                   / "!" / "#" / "$" / "&" / "+" / "-" / "."
+                   / "^" / "_" / "`" / "|" / "~"
+ */
+
+const char *h2_proxy_link_reverse_map(request_rec *r,
+                                      proxy_dir_conf *conf, 
+                                      const char *real_backend_uri,
+                                      const char *proxy_server_uri,
+                                      const char *s)
+{
+    link_ctx ctx;
+    
+    if (r->proxyreq != PROXYREQ_REVERSE) {
+        return s;
+    }
+    memset(&ctx, 0, sizeof(ctx));
+    ctx.r = r;
+    ctx.pool = r->pool;
+    ctx.conf = conf;
+    ctx.real_backend_uri = real_backend_uri;
+    ctx.rbu_len = (int)strlen(ctx.real_backend_uri);
+    ctx.p_server_uri = proxy_server_uri;
+    ctx.psu_len = (int)strlen(ctx.p_server_uri);
+    ctx.s = s;
+    ctx.slen = (int)strlen(s);
+    while (read_link(&ctx)) {
+        while (skip_param(&ctx)) {
+            /* nop */
+        }
+        map_link(&ctx);
+        if (!read_sep(&ctx)) {
+            break;
+        }
+    }
+    ap_log_rerror(APLOG_MARK, APLOG_TRACE2, 0, r, 
+                  "link_reverse_map %s --> %s", s, ctx.s);
+    return ctx.s;
+}
+
+/*******************************************************************************
+ * FIFO queue
+ ******************************************************************************/
+
+struct h2_proxy_fifo {
+    void **elems;
+    int nelems;
+    int set;
+    int head;
+    int count;
+    int aborted;
+    apr_thread_mutex_t *lock;
+    apr_thread_cond_t  *not_empty;
+    apr_thread_cond_t  *not_full;
+};
+
+static int nth_index(h2_proxy_fifo *fifo, int n) 
+{
+    return (fifo->head + n) % fifo->nelems;
+}
+
+static apr_status_t fifo_destroy(void *data) 
+{
+    h2_proxy_fifo *fifo = data;
+
+    apr_thread_cond_destroy(fifo->not_empty);
+    apr_thread_cond_destroy(fifo->not_full);
+    apr_thread_mutex_destroy(fifo->lock);
+
+    return APR_SUCCESS;
+}
+
+static int index_of(h2_proxy_fifo *fifo, void *elem)
+{
+    int i;
+    
+    for (i = 0; i < fifo->count; ++i) {
+        if (elem == fifo->elems[nth_index(fifo, i)]) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+static apr_status_t create_int(h2_proxy_fifo **pfifo, apr_pool_t *pool, 
+                               int capacity, int as_set)
+{
+    apr_status_t rv;
+    h2_proxy_fifo *fifo;
+    
+    fifo = apr_pcalloc(pool, sizeof(*fifo));
+    if (fifo == NULL) {
+        return APR_ENOMEM;
+    }
+
+    rv = apr_thread_mutex_create(&fifo->lock,
+                                 APR_THREAD_MUTEX_UNNESTED, pool);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    rv = apr_thread_cond_create(&fifo->not_empty, pool);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    rv = apr_thread_cond_create(&fifo->not_full, pool);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    fifo->elems = apr_pcalloc(pool, capacity * sizeof(void*));
+    if (fifo->elems == NULL) {
+        return APR_ENOMEM;
+    }
+    fifo->nelems = capacity;
+    fifo->set = as_set;
+    
+    *pfifo = fifo;
+    apr_pool_cleanup_register(pool, fifo, fifo_destroy, apr_pool_cleanup_null);
+
+    return APR_SUCCESS;
+}
+
+apr_status_t h2_proxy_fifo_create(h2_proxy_fifo **pfifo, apr_pool_t *pool, int capacity)
+{
+    return create_int(pfifo, pool, capacity, 0);
+}
+
+apr_status_t h2_proxy_fifo_set_create(h2_proxy_fifo **pfifo, apr_pool_t *pool, int capacity)
+{
+    return create_int(pfifo, pool, capacity, 1);
+}
+
+apr_status_t h2_proxy_fifo_term(h2_proxy_fifo *fifo)
+{
+    apr_status_t rv;
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        fifo->aborted = 1;
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+apr_status_t h2_proxy_fifo_interrupt(h2_proxy_fifo *fifo)
+{
+    apr_status_t rv;
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        apr_thread_cond_broadcast(fifo->not_empty);
+        apr_thread_cond_broadcast(fifo->not_full);
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+int h2_proxy_fifo_count(h2_proxy_fifo *fifo)
+{
+    return fifo->count;
+}
+
+int h2_proxy_fifo_capacity(h2_proxy_fifo *fifo)
+{
+    return fifo->nelems;
+}
+
+static apr_status_t check_not_empty(h2_proxy_fifo *fifo, int block)
+{
+    if (fifo->count == 0) {
+        if (!block) {
+            return APR_EAGAIN;
+        }
+        while (fifo->count == 0) {
+            if (fifo->aborted) {
+                return APR_EOF;
+            }
+            apr_thread_cond_wait(fifo->not_empty, fifo->lock);
+        }
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t fifo_push(h2_proxy_fifo *fifo, void *elem, int block)
+{
+    apr_status_t rv;
+    
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        if (fifo->set && index_of(fifo, elem) >= 0) {
+            /* set mode, elem already member */
+            apr_thread_mutex_unlock(fifo->lock);
+            return APR_EEXIST;
+        }
+        else if (fifo->count == fifo->nelems) {
+            if (block) {
+                while (fifo->count == fifo->nelems) {
+                    if (fifo->aborted) {
+                        apr_thread_mutex_unlock(fifo->lock);
+                        return APR_EOF;
+                    }
+                    apr_thread_cond_wait(fifo->not_full, fifo->lock);
+                }
+            }
+            else {
+                apr_thread_mutex_unlock(fifo->lock);
+                return APR_EAGAIN;
+            }
+        }
+        
+        ap_assert(fifo->count < fifo->nelems);
+        fifo->elems[nth_index(fifo, fifo->count)] = elem;
+        ++fifo->count;
+        if (fifo->count == 1) {
+            apr_thread_cond_broadcast(fifo->not_empty);
+        }
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+apr_status_t h2_proxy_fifo_push(h2_proxy_fifo *fifo, void *elem)
+{
+    return fifo_push(fifo, elem, 1);
+}
+
+apr_status_t h2_proxy_fifo_try_push(h2_proxy_fifo *fifo, void *elem)
+{
+    return fifo_push(fifo, elem, 0);
+}
+
+static void *pull_head(h2_proxy_fifo *fifo)
+{
+    void *elem;
+    
+    ap_assert(fifo->count > 0);
+    elem = fifo->elems[fifo->head];
+    --fifo->count;
+    if (fifo->count > 0) {
+        fifo->head = nth_index(fifo, 1);
+        if (fifo->count+1 == fifo->nelems) {
+            apr_thread_cond_broadcast(fifo->not_full);
+        }
+    }
+    return elem;
+}
+
+static apr_status_t fifo_pull(h2_proxy_fifo *fifo, void **pelem, int block)
+{
+    apr_status_t rv;
+    
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+    
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        if ((rv = check_not_empty(fifo, block)) != APR_SUCCESS) {
+            apr_thread_mutex_unlock(fifo->lock);
+            *pelem = NULL;
+            return rv;
+        }
+
+        ap_assert(fifo->count > 0);
+        *pelem = pull_head(fifo);
+
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+apr_status_t h2_proxy_fifo_pull(h2_proxy_fifo *fifo, void **pelem)
+{
+    return fifo_pull(fifo, pelem, 1);
+}
+
+apr_status_t h2_proxy_fifo_try_pull(h2_proxy_fifo *fifo, void **pelem)
+{
+    return fifo_pull(fifo, pelem, 0);
+}
+
+apr_status_t h2_proxy_fifo_remove(h2_proxy_fifo *fifo, void *elem)
+{
+    apr_status_t rv;
+    
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        int i, rc;
+        void *e;
+        
+        rc = 0;
+        for (i = 0; i < fifo->count; ++i) {
+            e = fifo->elems[nth_index(fifo, i)];
+            if (e == elem) {
+                ++rc;
+            }
+            else if (rc) {
+                fifo->elems[nth_index(fifo, i-rc)] = e;
+            }
+        }
+        if (rc) {
+            fifo->count -= rc;
+            if (fifo->count + rc == fifo->nelems) {
+                apr_thread_cond_broadcast(fifo->not_full);
+            }
+            rv = APR_SUCCESS;
+        }
+        else {
+            rv = APR_EAGAIN;
+        }
+        
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
diff -up --new-file httpd-2.4.23/modules/http2/h2_proxy_util.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_proxy_util.h
--- httpd-2.4.23/modules/http2/h2_proxy_util.h	2016-06-28 21:57:30.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_proxy_util.h	2017-04-10 17:04:55.000000000 +0200
@@ -19,27 +19,27 @@
 /*******************************************************************************
  * some debugging/format helpers
  ******************************************************************************/
-struct h2_request;
+struct h2_proxy_request;
 struct nghttp2_frame;
 
-int h2_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen);
+int h2_proxy_util_frame_print(const nghttp2_frame *frame, char *buffer, size_t maxlen);
 
 /*******************************************************************************
  * ihash - hash for structs with int identifier
  ******************************************************************************/
-typedef struct h2_ihash_t h2_ihash_t;
-typedef int h2_ihash_iter_t(void *ctx, void *val);
+typedef struct h2_proxy_ihash_t h2_proxy_ihash_t;
+typedef int h2_proxy_ihash_iter_t(void *ctx, void *val);
 
 /**
  * Create a hash for structures that have an identifying int member.
  * @param pool the pool to use
  * @param offset_of_int the offsetof() the int member in the struct
  */
-h2_ihash_t *h2_ihash_create(apr_pool_t *pool, size_t offset_of_int);
+h2_proxy_ihash_t *h2_proxy_ihash_create(apr_pool_t *pool, size_t offset_of_int);
 
-size_t h2_ihash_count(h2_ihash_t *ih);
-int h2_ihash_empty(h2_ihash_t *ih);
-void *h2_ihash_get(h2_ihash_t *ih, int id);
+size_t h2_proxy_ihash_count(h2_proxy_ihash_t *ih);
+int h2_proxy_ihash_empty(h2_proxy_ihash_t *ih);
+void *h2_proxy_ihash_get(h2_proxy_ihash_t *ih, int id);
 
 /**
  * Iterate over the hash members (without defined order) and invoke
@@ -49,26 +49,26 @@ void *h2_ihash_get(h2_ihash_t *ih, int i
  * @param ctx user supplied data passed into each iteration call
  * @return 0 if one iteration returned 0, otherwise != 0
  */
-int h2_ihash_iter(h2_ihash_t *ih, h2_ihash_iter_t *fn, void *ctx);
+int h2_proxy_ihash_iter(h2_proxy_ihash_t *ih, h2_proxy_ihash_iter_t *fn, void *ctx);
 
-void h2_ihash_add(h2_ihash_t *ih, void *val);
-void h2_ihash_remove(h2_ihash_t *ih, int id);
-void h2_ihash_remove_val(h2_ihash_t *ih, void *val);
-void h2_ihash_clear(h2_ihash_t *ih);
+void h2_proxy_ihash_add(h2_proxy_ihash_t *ih, void *val);
+void h2_proxy_ihash_remove(h2_proxy_ihash_t *ih, int id);
+void h2_proxy_ihash_remove_val(h2_proxy_ihash_t *ih, void *val);
+void h2_proxy_ihash_clear(h2_proxy_ihash_t *ih);
 
-size_t h2_ihash_shift(h2_ihash_t *ih, void **buffer, size_t max);
-size_t h2_ihash_ishift(h2_ihash_t *ih, int *buffer, size_t max);
+size_t h2_proxy_ihash_shift(h2_proxy_ihash_t *ih, void **buffer, size_t max);
+size_t h2_proxy_ihash_ishift(h2_proxy_ihash_t *ih, int *buffer, size_t max);
 
 /*******************************************************************************
  * iqueue - sorted list of int with user defined ordering
  ******************************************************************************/
-typedef struct h2_iqueue {
+typedef struct h2_proxy_iqueue {
     int *elts;
     int head;
     int nelts;
     int nalloc;
     apr_pool_t *pool;
-} h2_iqueue;
+} h2_proxy_iqueue;
 
 /**
  * Comparator for two int to determine their order.
@@ -81,26 +81,26 @@ typedef struct h2_iqueue {
  *     < 0: s1 should be sorted before s2
  *     > 0: s2 should be sorted before s1
  */
-typedef int h2_iq_cmp(int i1, int i2, void *ctx);
+typedef int h2_proxy_iq_cmp(int i1, int i2, void *ctx);
 
 /**
  * Allocate a new queue from the pool and initialize.
  * @param id the identifier of the queue
  * @param pool the memory pool
  */
-h2_iqueue *h2_iq_create(apr_pool_t *pool, int capacity);
+h2_proxy_iqueue *h2_proxy_iq_create(apr_pool_t *pool, int capacity);
 
 /**
  * Return != 0 iff there are no tasks in the queue.
  * @param q the queue to check
  */
-int h2_iq_empty(h2_iqueue *q);
+int h2_proxy_iq_empty(h2_proxy_iqueue *q);
 
 /**
  * Return the number of int in the queue.
  * @param q the queue to get size on
  */
-int h2_iq_count(h2_iqueue *q);
+int h2_proxy_iq_count(h2_proxy_iqueue *q);
 
 /**
  * Add a stream id to the queue. 
@@ -110,7 +110,7 @@ int h2_iq_count(h2_iqueue *q);
  * @param cmp the comparator for sorting
  * @param ctx user data for comparator 
  */
-void h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx);
+void h2_proxy_iq_add(h2_proxy_iqueue *q, int sid, h2_proxy_iq_cmp *cmp, void *ctx);
 
 /**
  * Remove the stream id from the queue. Return != 0 iff task
@@ -119,12 +119,12 @@ void h2_iq_add(h2_iqueue *q, int sid, h2
  * @param sid the stream id to remove
  * @return != 0 iff task was found in queue
  */
-int h2_iq_remove(h2_iqueue *q, int sid);
+int h2_proxy_iq_remove(h2_proxy_iqueue *q, int sid);
 
 /**
  * Remove all entries in the queue.
  */
-void h2_iq_clear(h2_iqueue *q);
+void h2_proxy_iq_clear(h2_proxy_iqueue *q);
 
 /**
  * Sort the stream idqueue again. Call if the task ordering
@@ -134,7 +134,7 @@ void h2_iq_clear(h2_iqueue *q);
  * @param cmp the comparator for sorting
  * @param ctx user data for the comparator 
  */
-void h2_iq_sort(h2_iqueue *q, h2_iq_cmp *cmp, void *ctx);
+void h2_proxy_iq_sort(h2_proxy_iqueue *q, h2_proxy_iq_cmp *cmp, void *ctx);
 
 /**
  * Get the first stream id from the queue or NULL if the queue is empty. 
@@ -143,39 +143,113 @@ void h2_iq_sort(h2_iqueue *q, h2_iq_cmp
  * @param q the queue to get the first task from
  * @return the first stream id of the queue, 0 if empty
  */
-int h2_iq_shift(h2_iqueue *q);
+int h2_proxy_iq_shift(h2_proxy_iqueue *q);
 
 /*******************************************************************************
  * common helpers
  ******************************************************************************/
-/* h2_log2(n) iff n is a power of 2 */
-unsigned char h2_log2(apr_uint32_t n);
+/* h2_proxy_log2(n) iff n is a power of 2 */
+unsigned char h2_proxy_log2(int n);
 
 /*******************************************************************************
  * HTTP/2 header helpers
  ******************************************************************************/
-void h2_util_camel_case_header(char *s, size_t len);
+void h2_proxy_util_camel_case_header(char *s, size_t len);
 int h2_proxy_res_ignore_header(const char *name, size_t len);
 
 /*******************************************************************************
  * nghttp2 helpers
  ******************************************************************************/
-typedef struct h2_ngheader {
+typedef struct h2_proxy_ngheader {
     nghttp2_nv *nv;
     apr_size_t nvlen;
-} h2_ngheader;
-h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, 
-                                       const struct h2_request *req);
+} h2_proxy_ngheader;
+h2_proxy_ngheader *h2_proxy_util_nghd_make_req(apr_pool_t *p, 
+                                               const struct h2_proxy_request *req);
 
 /*******************************************************************************
- * h2_request helpers
- ******************************************************************************/
-struct h2_request *h2_req_create(int id, apr_pool_t *pool, int serialize);
-apr_status_t h2_req_make(struct h2_request *req, apr_pool_t *pool,
-                         const char *method, const char *scheme, 
-                         const char *authority, const char *path, 
-                         apr_table_t *headers);
+ * h2_proxy_request helpers
+ ******************************************************************************/
+typedef struct h2_proxy_request h2_proxy_request;
+
+struct h2_proxy_request {
+    const char *method; /* pseudo header values, see ch. 8.1.2.3 */
+    const char *scheme;
+    const char *authority;
+    const char *path;
+    
+    apr_table_t *headers;
+    
+    apr_time_t request_time;
+    
+    unsigned int chunked : 1;   /* iff requst body needs to be forwarded as chunked */
+    unsigned int serialize : 1; /* iff this request is written in HTTP/1.1 serialization */
+};
+
+h2_proxy_request *h2_proxy_req_create(int id, apr_pool_t *pool, int serialize);
+apr_status_t h2_proxy_req_make(h2_proxy_request *req, apr_pool_t *pool,
+                               const char *method, const char *scheme, 
+                               const char *authority, const char *path, 
+                               apr_table_t *headers);
+
+/*******************************************************************************
+ * reverse mapping for link headers
+ ******************************************************************************/
+const char *h2_proxy_link_reverse_map(request_rec *r,
+                                      proxy_dir_conf *conf, 
+                                      const char *real_server_uri,
+                                      const char *proxy_server_uri,
+                                      const char *s);
+
+/*******************************************************************************
+ * FIFO queue
+ ******************************************************************************/
+
+/**
+ * A thread-safe FIFO queue with some extra bells and whistles, if you
+ * do not need anything special, better use 'apr_queue'.
+ */
+typedef struct h2_proxy_fifo h2_proxy_fifo;
+
+/**
+ * Create a FIFO queue that can hold up to capacity elements. Elements can
+ * appear several times.
+ */
+apr_status_t h2_proxy_fifo_create(h2_proxy_fifo **pfifo, apr_pool_t *pool, int capacity);
+
+/**
+ * Create a FIFO set that can hold up to capacity elements. Elements only
+ * appear once. Pushing an element already present does not change the
+ * queue and is successful.
+ */
+apr_status_t h2_proxy_fifo_set_create(h2_proxy_fifo **pfifo, apr_pool_t *pool, int capacity);
+
+apr_status_t h2_proxy_fifo_term(h2_proxy_fifo *fifo);
+apr_status_t h2_proxy_fifo_interrupt(h2_proxy_fifo *fifo);
+
+int h2_proxy_fifo_capacity(h2_proxy_fifo *fifo);
+int h2_proxy_fifo_count(h2_proxy_fifo *fifo);
 
+/**
+ * Push en element into the queue. Blocks if there is no capacity left.
+ * 
+ * @param fifo the FIFO queue
+ * @param elem the element to push
+ * @return APR_SUCCESS on push, APR_EAGAIN on try_push on a full queue,
+ *         APR_EEXIST when in set mode and elem already there.
+ */
+apr_status_t h2_proxy_fifo_push(h2_proxy_fifo *fifo, void *elem);
+apr_status_t h2_proxy_fifo_try_push(h2_proxy_fifo *fifo, void *elem);
+
+apr_status_t h2_proxy_fifo_pull(h2_proxy_fifo *fifo, void **pelem);
+apr_status_t h2_proxy_fifo_try_pull(h2_proxy_fifo *fifo, void **pelem);
+
+/**
+ * Remove the elem from the queue, will remove multiple appearances.
+ * @param elem  the element to remove
+ * @return APR_SUCCESS iff > 0 elems were removed, APR_EAGAIN otherwise.
+ */
+apr_status_t h2_proxy_fifo_remove(h2_proxy_fifo *fifo, void *elem);
 
 
 #endif /* defined(__mod_h2__h2_proxy_util__) */
diff -up --new-file httpd-2.4.23/modules/http2/h2_push.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_push.c
--- httpd-2.4.23/modules/http2/h2_push.c	2016-05-23 12:55:29.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_push.c	2017-02-14 16:53:50.000000000 +0100
@@ -34,7 +34,7 @@
 #include "h2_util.h"
 #include "h2_push.h"
 #include "h2_request.h"
-#include "h2_response.h"
+#include "h2_headers.h"
 #include "h2_session.h"
 #include "h2_stream.h"
 
@@ -58,6 +58,7 @@ static const char *policy_str(h2_push_po
 
 typedef struct {
     const h2_request *req;
+    int push_policy;
     apr_pool_t *pool;
     apr_array_header_t *pushes;
     const char *s;
@@ -162,10 +163,10 @@ static char *mk_str(link_ctx *ctx, size_
     if (ctx->i < end) {
         return apr_pstrndup(ctx->pool, ctx->s + ctx->i, end - ctx->i);
     }
-    return "";
+    return (char*)"";
 }
 
-static int read_qstring(link_ctx *ctx, char **ps)
+static int read_qstring(link_ctx *ctx, const char **ps)
 {
     if (skip_ws(ctx) && read_chr(ctx, '\"')) {
         size_t end;
@@ -178,7 +179,7 @@ static int read_qstring(link_ctx *ctx, c
     return 0;
 }
 
-static int read_ptoken(link_ctx *ctx, char **ps)
+static int read_ptoken(link_ctx *ctx, const char **ps)
 {
     if (skip_ws(ctx)) {
         size_t i;
@@ -208,7 +209,7 @@ static int read_link(link_ctx *ctx)
     return 0;
 }
 
-static int read_pname(link_ctx *ctx, char **pname)
+static int read_pname(link_ctx *ctx, const char **pname)
 {
     if (skip_ws(ctx)) {
         size_t i;
@@ -224,7 +225,7 @@ static int read_pname(link_ctx *ctx, cha
     return 0;
 }
 
-static int read_pvalue(link_ctx *ctx, char **pvalue)
+static int read_pvalue(link_ctx *ctx, const char **pvalue)
 {
     if (skip_ws(ctx) && read_chr(ctx, '=')) {
         if (read_qstring(ctx, pvalue) || read_ptoken(ctx, pvalue)) {
@@ -237,7 +238,7 @@ static int read_pvalue(link_ctx *ctx, ch
 static int read_param(link_ctx *ctx)
 {
     if (skip_ws(ctx) && read_chr(ctx, ';')) {
-        char *name, *value = "";
+        const char *name, *value = "";
         if (read_pname(ctx, &name)) {
             read_pvalue(ctx, &value); /* value is optional */
             apr_table_setn(ctx->params, name, value);
@@ -336,7 +337,7 @@ static int add_push(link_ctx *ctx)
                  */
                 path = apr_uri_unparse(ctx->pool, &uri, APR_URI_UNP_OMITSITEPART);
                 push = apr_pcalloc(ctx->pool, sizeof(*push));
-                switch (ctx->req->push_policy) {
+                switch (ctx->push_policy) {
                     case H2_PUSH_HEAD:
                         method = "HEAD";
                         break;
@@ -346,13 +347,17 @@ static int add_push(link_ctx *ctx)
                 }
                 headers = apr_table_make(ctx->pool, 5);
                 apr_table_do(set_push_header, headers, ctx->req->headers, NULL);
-                req = h2_req_createn(0, ctx->pool, method, ctx->req->scheme,
-                                     ctx->req->authority, path, headers,
-                                     ctx->req->serialize);
+                req = h2_req_create(0, ctx->pool, method, ctx->req->scheme,
+                                    ctx->req->authority, path, headers,
+                                    ctx->req->serialize);
                 /* atm, we do not push on pushes */
-                h2_request_end_headers(req, ctx->pool, 1, 0);
+                h2_request_end_headers(req, ctx->pool, 1);
                 push->req = req;
-                
+                if (has_param(ctx, "critical")) {
+                    h2_priority *prio = apr_pcalloc(ctx->pool, sizeof(*prio));
+                    prio->dependency = H2_DEPENDANT_BEFORE;
+                    push->priority = prio;
+                }
                 if (!ctx->pushes) {
                     ctx->pushes = apr_array_make(ctx->pool, 5, sizeof(h2_push*));
                 }
@@ -427,10 +432,10 @@ static int head_iter(void *ctx, const ch
     return 1;
 }
 
-apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req, 
-                                    const h2_response *res)
+apr_array_header_t *h2_push_collect(apr_pool_t *p, const h2_request *req,
+                                    int push_policy, const h2_headers *res)
 {
-    if (req && req->push_policy != H2_PUSH_NONE) {
+    if (req && push_policy != H2_PUSH_NONE) {
         /* Collect push candidates from the request/response pair.
          * 
          * One source for pushes are "rel=preload" link headers
@@ -444,11 +449,13 @@ apr_array_header_t *h2_push_collect(apr_
             
             memset(&ctx, 0, sizeof(ctx));
             ctx.req = req;
+            ctx.push_policy = push_policy;
             ctx.pool = p;
             
             apr_table_do(head_iter, &ctx, res->headers, NULL);
             if (ctx.pushes) {
-                apr_table_setn(res->headers, "push-policy", policy_str(req->push_policy));
+                apr_table_setn(res->headers, "push-policy", 
+                               policy_str(push_policy));
             }
             return ctx.pushes;
         }
@@ -527,9 +534,9 @@ static unsigned int val_apr_hash(const c
 static void calc_apr_hash(h2_push_diary *diary, apr_uint64_t *phash, h2_push *push) 
 {
     apr_uint64_t val;
-#if APR_UINT64MAX > APR_UINT_MAX
-    val = (val_apr_hash(push->req->scheme) << 32);
-    val ^= (val_apr_hash(push->req->authority) << 16);
+#if APR_UINT64_MAX > UINT_MAX
+    val = ((apr_uint64_t)(val_apr_hash(push->req->scheme)) << 32);
+    val ^= ((apr_uint64_t)(val_apr_hash(push->req->authority)) << 16);
     val ^= val_apr_hash(push->req->path);
 #else
     val = val_apr_hash(push->req->scheme);
@@ -552,7 +559,7 @@ static apr_int32_t ceil_power_of_2(apr_i
 }
 
 static h2_push_diary *diary_create(apr_pool_t *p, h2_push_digest_type dtype, 
-                                   apr_size_t N)
+                                   int N)
 {
     h2_push_diary *diary = NULL;
     
@@ -561,7 +568,7 @@ static h2_push_diary *diary_create(apr_p
         
         diary->NMax        = ceil_power_of_2(N);
         diary->N           = diary->NMax;
-        /* the mask we use in value comparision depends on where we got
+        /* the mask we use in value comparison depends on where we got
          * the values from. If we calculate them ourselves, we can use
          * the full 64 bits.
          * If we set the diary via a compressed golomb set, we have less
@@ -587,7 +594,7 @@ static h2_push_diary *diary_create(apr_p
     return diary;
 }
 
-h2_push_diary *h2_push_diary_create(apr_pool_t *p, apr_size_t N)
+h2_push_diary *h2_push_diary_create(apr_pool_t *p, int N)
 {
     return diary_create(p, H2_PUSH_DIGEST_SHA256, N);
 }
@@ -681,7 +688,7 @@ apr_array_header_t *h2_push_diary_update
     
 apr_array_header_t *h2_push_collect_update(h2_stream *stream, 
                                            const struct h2_request *req, 
-                                           const struct h2_response *res)
+                                           const struct h2_headers *res)
 {
     h2_session *session = stream->session;
     const char *cache_digest = apr_table_get(req->headers, "Cache-Digest");
@@ -693,12 +700,11 @@ apr_array_header_t *h2_push_collect_upda
                                             cache_digest, stream->pool);
         if (status != APR_SUCCESS) {
             ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
-                          APLOGNO(03057)
-                          "h2_session(%ld): push diary set from Cache-Digest: %s", 
-                          session->id, cache_digest);
+                          H2_SSSN_LOG(APLOGNO(03057), session,
+                          "push diary set from Cache-Digest: %s"), cache_digest);
         }
     }
-    pushes = h2_push_collect(stream->pool, req, res);
+    pushes = h2_push_collect(stream->pool, req, stream->push_policy, res);
     return h2_push_diary_update(stream->session, pushes);
 }
 
@@ -711,9 +717,9 @@ static apr_int32_t h2_log2inv(unsigned c
 typedef struct {
     h2_push_diary *diary;
     unsigned char log2p;
-    apr_uint32_t mask_bits;
-    apr_uint32_t delta_bits;
-    apr_uint32_t fixed_bits;
+    int mask_bits;
+    int delta_bits;
+    int fixed_bits;
     apr_uint64_t fixed_mask;
     apr_pool_t *pool;
     unsigned char *data;
@@ -812,10 +818,10 @@ static apr_status_t gset_encode_next(gse
  * @param plen on successful return, the length of the binary data
  */
 apr_status_t h2_push_diary_digest_get(h2_push_diary *diary, apr_pool_t *pool, 
-                                      apr_uint32_t maxP, const char *authority, 
+                                      int maxP, const char *authority, 
                                       const char **pdata, apr_size_t *plen)
 {
-    apr_size_t nelts, N, i;
+    int nelts, N, i;
     unsigned char log2n, log2pmax;
     gset_encoder encoder;
     apr_uint64_t *hashes;
@@ -965,7 +971,7 @@ apr_status_t h2_push_diary_digest_set(h2
 {
     gset_decoder decoder;
     unsigned char log2n, log2p;
-    apr_size_t N, i;
+    int N, i;
     apr_pool_t *pool = diary->entries->pool;
     h2_push_diary_entry e;
     apr_status_t status = APR_SUCCESS;
diff -up --new-file httpd-2.4.23/modules/http2/h2_push.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_push.h
--- httpd-2.4.23/modules/http2/h2_push.h	2016-05-30 21:58:14.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_push.h	2016-11-14 12:15:08.000000000 +0100
@@ -18,13 +18,14 @@
 #include "h2.h"
 
 struct h2_request;
-struct h2_response;
+struct h2_headers;
 struct h2_ngheader;
 struct h2_session;
 struct h2_stream;
 
 typedef struct h2_push {
     const struct h2_request *req;
+    h2_priority *priority;
 } h2_push;
 
 typedef enum {
@@ -38,8 +39,8 @@ typedef void h2_push_digest_calc(h2_push
 
 struct h2_push_diary {
     apr_array_header_t  *entries;
-    apr_size_t           NMax; /* Maximum for N, should size change be necessary */
-    apr_size_t           N;    /* Current maximum number of entries, power of 2 */
+    int         NMax; /* Maximum for N, should size change be necessary */
+    int         N;    /* Current maximum number of entries, power of 2 */
     apr_uint64_t         mask; /* mask for relevant bits */
     unsigned int         mask_bits; /* number of relevant bits */
     const char          *authority;
@@ -58,7 +59,8 @@ struct h2_push_diary {
  */
 apr_array_header_t *h2_push_collect(apr_pool_t *p, 
                                     const struct h2_request *req, 
-                                    const struct h2_response *res);
+                                    int push_policy, 
+                                    const struct h2_headers *res);
 
 /**
  * Create a new push diary for the given maximum number of entries.
@@ -67,7 +69,7 @@ apr_array_header_t *h2_push_collect(apr_
  * @param N the max number of entries, rounded up to 2^x
  * @return the created diary, might be NULL of max_entries is 0
  */
-h2_push_diary *h2_push_diary_create(apr_pool_t *p, apr_size_t N);
+h2_push_diary *h2_push_diary_create(apr_pool_t *p, int N);
 
 /**
  * Filters the given pushes against the diary and returns only those pushes
@@ -81,7 +83,7 @@ apr_array_header_t *h2_push_diary_update
  */
 apr_array_header_t *h2_push_collect_update(struct h2_stream *stream, 
                                            const struct h2_request *req, 
-                                           const struct h2_response *res);
+                                           const struct h2_headers *res);
 /**
  * Get a cache digest as described in 
  * https://datatracker.ietf.org/doc/draft-kazuho-h2-cache-digest/
@@ -94,7 +96,7 @@ apr_array_header_t *h2_push_collect_upda
  * @param plen on successful return, the length of the binary data
  */
 apr_status_t h2_push_diary_digest_get(h2_push_diary *diary, apr_pool_t *p, 
-                                      apr_uint32_t maxP, const char *authority, 
+                                      int maxP, const char *authority, 
                                       const char **pdata, apr_size_t *plen);
 
 /**
diff -up --new-file httpd-2.4.23/modules/http2/h2_request.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_request.c
--- httpd-2.4.23/modules/http2/h2_request.c	2016-05-04 15:58:02.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_request.c	2017-04-18 15:12:38.000000000 +0200
@@ -30,27 +30,43 @@
 #include <scoreboard.h>
 
 #include "h2_private.h"
+#include "h2_config.h"
 #include "h2_push.h"
 #include "h2_request.h"
 #include "h2_util.h"
 
 
-static apr_status_t inspect_clen(h2_request *req, const char *s)
+typedef struct {
+    apr_table_t *headers;
+    apr_pool_t *pool;
+    apr_status_t status;
+} h1_ctx;
+
+static int set_h1_header(void *ctx, const char *key, const char *value)
 {
-    char *end;
-    req->content_length = apr_strtoi64(s, &end, 10);
-    return (s == end)? APR_EINVAL : APR_SUCCESS;
+    h1_ctx *x = ctx;
+    x->status = h2_req_add_header(x->headers, x->pool, key, strlen(key), 
+                                  value, strlen(value));
+    return (x->status == APR_SUCCESS)? 1 : 0;
 }
 
-apr_status_t h2_request_rwrite(h2_request *req, apr_pool_t *pool, 
-                               request_rec *r)
+apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, 
+                                request_rec *r)
 {
-    apr_status_t status;
-    const char *scheme, *authority;
+    h2_request *req;
+    const char *scheme, *authority, *path;
+    h1_ctx x;
     
+    *preq = NULL;
     scheme = apr_pstrdup(pool, r->parsed_uri.scheme? r->parsed_uri.scheme
               : ap_http_scheme(r));
     authority = apr_pstrdup(pool, r->hostname);
+    path = apr_uri_unparse(pool, &r->parsed_uri, APR_URI_UNP_OMITSITEPART);
+    
+    if (!r->method || !scheme || !r->hostname || !path) {
+        return APR_EINVAL;
+    }
+
     if (!ap_strchr_c(authority, ':') && r->server && r->server->port) {
         apr_port_t defport = apr_uri_port_of_scheme(scheme);
         if (defport != r->server->port) {
@@ -60,11 +76,24 @@ apr_status_t h2_request_rwrite(h2_reques
         }
     }
     
-    status = h2_req_make(req, pool, apr_pstrdup(pool, r->method), scheme, 
-                         authority, apr_uri_unparse(pool, &r->parsed_uri, 
-                                                    APR_URI_UNP_OMITSITEPART),
-                         r->headers_in);
-    return status;
+    req = apr_pcalloc(pool, sizeof(*req));
+    req->method    = apr_pstrdup(pool, r->method);
+    req->scheme    = scheme;
+    req->authority = authority;
+    req->path      = path;
+    req->headers   = apr_table_make(pool, 10);
+    if (r->server) {
+        req->serialize = h2_config_geti(h2_config_sget(r->server), 
+                                        H2_CONF_SER_HEADERS);
+    }
+
+    x.pool = pool;
+    x.headers = req->headers;
+    x.status = APR_SUCCESS;
+    apr_table_do(set_h1_header, &x, r->headers_in, NULL);
+    
+    *preq = req;
+    return x.status;
 }
 
 apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool, 
@@ -82,8 +111,7 @@ apr_status_t h2_request_add_header(h2_re
         if (!apr_is_empty_table(req->headers)) {
             ap_log_perror(APLOG_MARK, APLOG_ERR, 0, pool,
                           APLOGNO(02917) 
-                          "h2_request(%d): pseudo header after request start",
-                          req->id);
+                          "h2_request: pseudo header after request start");
             return APR_EGENERAL;
         }
         
@@ -109,28 +137,22 @@ apr_status_t h2_request_add_header(h2_re
             strncpy(buffer, name, (nlen > 31)? 31 : nlen);
             ap_log_perror(APLOG_MARK, APLOG_WARNING, 0, pool,
                           APLOGNO(02954) 
-                          "h2_request(%d): ignoring unknown pseudo header %s",
-                          req->id, buffer);
+                          "h2_request: ignoring unknown pseudo header %s",
+                          buffer);
         }
     }
     else {
         /* non-pseudo header, append to work bucket of stream */
-        status = h2_headers_add_h1(req->headers, pool, name, nlen, value, vlen);
+        status = h2_req_add_header(req->headers, pool, name, nlen, value, vlen);
     }
     
     return status;
 }
 
-apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, 
-                                    int eos, int push)
+apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos)
 {
     const char *s;
     
-    if (req->eoh) {
-        /* already done */
-        return APR_SUCCESS;
-    }
-
     /* rfc7540, ch. 8.1.2.3:
      * - if we have :authority, it overrides any Host header 
      * - :authority MUST be ommited when converting h1->h2, so we
@@ -147,18 +169,11 @@ apr_status_t h2_request_end_headers(h2_r
     }
 
     s = apr_table_get(req->headers, "Content-Length");
-    if (s) {
-        if (inspect_clen(req, s) != APR_SUCCESS) {
-            ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool,
-                          APLOGNO(02959) 
-                          "h2_request(%d): content-length value not parsed: %s",
-                          req->id, s);
-            return APR_EINVAL;
-        }
-    }
-    else {
-        /* no content-length given */
-        req->content_length = -1;
+    if (!s) {
+        /* HTTP/2 does not need a Content-Length for framing, but our
+         * internal request processing is used to HTTP/1.1, so we
+         * need to either add a Content-Length or a Transfer-Encoding
+         * if any content can be expected. */
         if (!eos) {
             /* We have not seen a content-length and have no eos,
              * simulate a chunked encoding for our HTTP/1.1 infrastructure,
@@ -168,68 +183,16 @@ apr_status_t h2_request_end_headers(h2_r
             apr_table_mergen(req->headers, "Transfer-Encoding", "chunked");
         }
         else if (apr_table_get(req->headers, "Content-Type")) {
-            /* If we have a content-type, but already see eos, no more
+            /* If we have a content-type, but already seen eos, no more
              * data will come. Signal a zero content length explicitly.
              */
             apr_table_setn(req->headers, "Content-Length", "0");
         }
     }
 
-    req->eoh = 1;
-    h2_push_policy_determine(req, pool, push);
-    
-    /* In the presence of trailers, force behaviour of chunked encoding */
-    s = apr_table_get(req->headers, "Trailer");
-    if (s && s[0]) {
-        req->trailers = apr_table_make(pool, 5);
-        if (!req->chunked) {
-            req->chunked = 1;
-            apr_table_mergen(req->headers, "Transfer-Encoding", "chunked");
-        }
-    }
-    
-    return APR_SUCCESS;
-}
-
-static apr_status_t add_h1_trailer(h2_request *req, apr_pool_t *pool, 
-                                   const char *name, size_t nlen,
-                                   const char *value, size_t vlen)
-{
-    char *hname, *hvalue;
-    
-    if (h2_req_ignore_trailer(name, nlen)) {
-        return APR_SUCCESS;
-    }
-    
-    hname = apr_pstrndup(pool, name, nlen);
-    hvalue = apr_pstrndup(pool, value, vlen);
-    h2_util_camel_case_header(hname, nlen);
-
-    apr_table_mergen(req->trailers, hname, hvalue);
-    
     return APR_SUCCESS;
 }
 
-
-apr_status_t h2_request_add_trailer(h2_request *req, apr_pool_t *pool,
-                                    const char *name, size_t nlen,
-                                    const char *value, size_t vlen)
-{
-    if (!req->trailers) {
-        ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, pool, APLOGNO(03059)
-                      "h2_request(%d): unanounced trailers",
-                      req->id);
-        return APR_EINVAL;
-    }
-    if (nlen == 0 || name[0] == ':') {
-        ap_log_perror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, pool, APLOGNO(03060)
-                      "h2_request(%d): pseudo header in trailer",
-                      req->id);
-        return APR_EINVAL;
-    }
-    return add_h1_trailer(req, pool, name, nlen, value, vlen);
-}
-
 h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src)
 {
     h2_request *dst = apr_pmemdup(p, src, sizeof(*dst));
@@ -238,25 +201,24 @@ h2_request *h2_request_clone(apr_pool_t
     dst->authority    = apr_pstrdup(p, src->authority);
     dst->path         = apr_pstrdup(p, src->path);
     dst->headers      = apr_table_clone(p, src->headers);
-    if (src->trailers) {
-        dst->trailers = apr_table_clone(p, src->trailers);
-    }
     return dst;
 }
 
-request_rec *h2_request_create_rec(const h2_request *req, conn_rec *conn)
+request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
 {
-    request_rec *r;
-    apr_pool_t *p;
     int access_status = HTTP_OK;    
-    
-    apr_pool_create(&p, conn->pool);
+    const char *rpath;
+    apr_pool_t *p;
+    request_rec *r;
+    const char *s;
+
+    apr_pool_create(&p, c->pool);
     apr_pool_tag(p, "request");
     r = apr_pcalloc(p, sizeof(request_rec));
-    AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)conn);
+    AP_READ_REQUEST_ENTRY((intptr_t)r, (uintptr_t)c);
     r->pool            = p;
-    r->connection      = conn;
-    r->server          = conn->base_server;
+    r->connection      = c;
+    r->server          = c->base_server;
     
     r->user            = NULL;
     r->ap_auth_type    = NULL;
@@ -274,9 +236,9 @@ request_rec *h2_request_create_rec(const
     r->request_config  = ap_create_request_config(r->pool);
     /* Must be set before we run create request hook */
     
-    r->proto_output_filters = conn->output_filters;
+    r->proto_output_filters = c->output_filters;
     r->output_filters  = r->proto_output_filters;
-    r->proto_input_filters = conn->input_filters;
+    r->proto_input_filters = c->input_filters;
     r->input_filters   = r->proto_input_filters;
     ap_run_create_request(r);
     r->per_dir_config  = r->server->lookup_defaults;
@@ -295,10 +257,10 @@ request_rec *h2_request_create_rec(const
      */
     r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
     
-    r->useragent_addr = conn->client_addr;
-    r->useragent_ip = conn->client_ip;
+    r->useragent_addr = c->client_addr;
+    r->useragent_ip = c->client_ip;
     
-    ap_run_pre_read_request(r, conn);
+    ap_run_pre_read_request(r, c);
     
     /* Time to populate r with the data we have. */
     r->request_time = req->request_time;
@@ -309,12 +271,13 @@ request_rec *h2_request_create_rec(const
         r->header_only = 1;
     }
 
-    ap_parse_uri(r, req->path);
-    r->protocol = "HTTP/2.0";
+    rpath = (req->path ? req->path : "");
+    ap_parse_uri(r, rpath);
+    r->protocol = (char*)"HTTP/2.0";
     r->proto_num = HTTP_VERSION(2, 0);
 
     r->the_request = apr_psprintf(r->pool, "%s %s %s", 
-                                  r->method, req->path, r->protocol);
+                                  r->method, rpath, r->protocol);
     
     /* update what we think the virtual host is based on the headers we've
      * now read. may update status.
@@ -327,6 +290,17 @@ request_rec *h2_request_create_rec(const
     /* we may have switched to another server */
     r->per_dir_config = r->server->lookup_defaults;
     
+    s = apr_table_get(r->headers_in, "Expect");
+    if (s && s[0]) {
+        if (ap_cstr_casecmp(s, "100-continue") == 0) {
+            r->expecting_100 = 1;
+        }
+        else {
+            r->status = HTTP_EXPECTATION_FAILED;
+            ap_send_error_response(r, 0);
+        }
+    }
+
     /*
      * Add the HTTP_IN filter here to ensure that ap_discard_request_body
      * called by ap_die and by ap_send_error_response works correctly on
@@ -341,16 +315,16 @@ request_rec *h2_request_create_rec(const
         /* Request check post hooks failed. An example of this would be a
          * request for a vhost where h2 is disabled --> 421.
          */
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, conn, APLOGNO()
-                      "h2_request(%d): access_status=%d, request_create failed",
-                      req->id, access_status);
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03367)
+                      "h2_request: access_status=%d, request_create failed",
+                      access_status);
         ap_die(access_status, r);
-        ap_update_child_status(conn->sbh, SERVER_BUSY_LOG, r);
+        ap_update_child_status(c->sbh, SERVER_BUSY_LOG, r);
         ap_run_log_transaction(r);
         r = NULL;
         goto traceout;
     }
-    
+
     AP_READ_REQUEST_SUCCESS((uintptr_t)r, (char *)r->method, 
                             (char *)r->uri, (char *)r->server->defn_name, 
                             r->status);
diff -up --new-file httpd-2.4.23/modules/http2/h2_request.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_request.h
--- httpd-2.4.23/modules/http2/h2_request.h	2016-05-04 15:58:02.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_request.h	2016-10-03 14:57:47.000000000 +0200
@@ -18,8 +18,8 @@
 
 #include "h2.h"
 
-apr_status_t h2_request_rwrite(h2_request *req, apr_pool_t *pool, 
-                               request_rec *r);
+apr_status_t h2_request_rcreate(h2_request **preq, apr_pool_t *pool, 
+                                request_rec *r);
 
 apr_status_t h2_request_add_header(h2_request *req, apr_pool_t *pool,
                                    const char *name, size_t nlen,
@@ -29,8 +29,7 @@ apr_status_t h2_request_add_trailer(h2_r
                                     const char *name, size_t nlen,
                                     const char *value, size_t vlen);
 
-apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, 
-                                    int eos, int push);
+apr_status_t h2_request_end_headers(h2_request *req, apr_pool_t *pool, int eos);
 
 h2_request *h2_request_clone(apr_pool_t *p, const h2_request *src);
 
diff -up --new-file httpd-2.4.23/modules/http2/h2_response.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_response.c
--- httpd-2.4.23/modules/http2/h2_response.c	2016-05-18 17:10:20.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_response.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,205 +0,0 @@
-/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <assert.h>
-#include <stdio.h>
-
-#include <apr_strings.h>
-
-#include <httpd.h>
-#include <http_core.h>
-#include <http_log.h>
-#include <util_time.h>
-
-#include <nghttp2/nghttp2.h>
-
-#include "h2_private.h"
-#include "h2_filter.h"
-#include "h2_h2.h"
-#include "h2_util.h"
-#include "h2_request.h"
-#include "h2_response.h"
-
-
-static apr_table_t *parse_headers(apr_array_header_t *hlines, apr_pool_t *pool)
-{
-    if (hlines) {
-        apr_table_t *headers = apr_table_make(pool, hlines->nelts);        
-        int i;
-        
-        for (i = 0; i < hlines->nelts; ++i) {
-            char *hline = ((char **)hlines->elts)[i];
-            char *sep = ap_strchr(hline, ':');
-            if (!sep) {
-                ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, pool,
-                              APLOGNO(02955) "h2_response: invalid header[%d] '%s'",
-                              i, (char*)hline);
-                /* not valid format, abort */
-                return NULL;
-            }
-            (*sep++) = '\0';
-            while (*sep == ' ' || *sep == '\t') {
-                ++sep;
-            }
-            
-            if (!h2_util_ignore_header(hline)) {
-                apr_table_merge(headers, hline, sep);
-            }
-        }
-        return headers;
-    }
-    else {
-        return apr_table_make(pool, 0);        
-    }
-}
-
-static const char *get_sos_filter(apr_table_t *notes) 
-{
-    return notes? apr_table_get(notes, H2_RESP_SOS_NOTE) : NULL;
-}
-
-static void check_clen(h2_response *response, request_rec *r, apr_pool_t *pool)
-{
-    
-    if (r && r->header_only) {
-        response->content_length = 0;
-    }
-    else if (response->headers) {
-        const char *s = apr_table_get(response->headers, "Content-Length");
-        if (s) {
-            char *end;
-            response->content_length = apr_strtoi64(s, &end, 10);
-            if (s == end) {
-                ap_log_perror(APLOG_MARK, APLOG_WARNING, APR_EINVAL, 
-                              pool, APLOGNO(02956) 
-                              "h2_response: content-length"
-                              " value not parsed: %s", s);
-                response->content_length = -1;
-            }
-        }
-    }
-}
-
-static h2_response *h2_response_create_int(int stream_id,
-                                           int rst_error,
-                                           int http_status,
-                                           apr_table_t *headers,
-                                           apr_table_t *notes,
-                                           apr_pool_t *pool)
-{
-    h2_response *response;
-
-    if (!headers) {
-        return NULL;
-    }
-    
-    response = apr_pcalloc(pool, sizeof(h2_response));
-    if (response == NULL) {
-        return NULL;
-    }
-    
-    response->stream_id      = stream_id;
-    response->rst_error      = rst_error;
-    response->http_status    = http_status? http_status : 500;
-    response->content_length = -1;
-    response->headers        = headers;
-    response->sos_filter     = get_sos_filter(notes);
-    
-    check_clen(response, NULL, pool);
-    return response;
-}
-
-
-h2_response *h2_response_create(int stream_id,
-                                int rst_error,
-                                int http_status,
-                                apr_array_header_t *hlines,
-                                apr_table_t *notes,
-                                apr_pool_t *pool)
-{
-    return h2_response_create_int(stream_id, rst_error, http_status,
-                                  parse_headers(hlines, pool), notes, pool);
-}
-
-h2_response *h2_response_rcreate(int stream_id, request_rec *r,
-                                 apr_table_t *header, apr_pool_t *pool)
-{
-    h2_response *response = apr_pcalloc(pool, sizeof(h2_response));
-    if (response == NULL) {
-        return NULL;
-    }
-    
-    response->stream_id      = stream_id;
-    response->http_status    = r->status;
-    response->content_length = -1;
-    response->headers        = header;
-    response->sos_filter     = get_sos_filter(r->notes);
-
-    check_clen(response, r, pool);
-    
-    if (response->http_status == HTTP_FORBIDDEN) {
-        const char *cause = apr_table_get(r->notes, "ssl-renegotiate-forbidden");
-        if (cause) {
-            /* This request triggered a TLS renegotiation that is now allowed 
-             * in HTTP/2. Tell the client that it should use HTTP/1.1 for this.
-             */
-            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, response->http_status, r,
-                          APLOGNO(03061) 
-                          "h2_response(%ld-%d): renegotiate forbidden, cause: %s",
-                          (long)r->connection->id, stream_id, cause);
-            response->rst_error = H2_ERR_HTTP_1_1_REQUIRED;
-        }
-    }
-    
-    return response;
-}
-
-h2_response *h2_response_die(int stream_id, apr_status_t type,
-                             const struct h2_request *req, apr_pool_t *pool)
-{
-    apr_table_t *headers = apr_table_make(pool, 5);
-    char *date = NULL;
-    int status = (type >= 200 && type < 600)? type : 500;
-    
-    date = apr_palloc(pool, APR_RFC822_DATE_LEN);
-    ap_recent_rfc822_date(date, req->request_time);
-    apr_table_setn(headers, "Date", date);
-    apr_table_setn(headers, "Server", ap_get_server_banner());
-    
-    return h2_response_create_int(stream_id, 0, status, headers, NULL, pool);
-}
-
-h2_response *h2_response_clone(apr_pool_t *pool, h2_response *from)
-{
-    h2_response *to = apr_pcalloc(pool, sizeof(h2_response));
-    
-    to->stream_id      = from->stream_id;
-    to->http_status    = from->http_status;
-    to->content_length = from->content_length;
-    to->sos_filter     = from->sos_filter;
-    if (from->headers) {
-        to->headers    = apr_table_clone(pool, from->headers);
-    }
-    if (from->trailers) {
-        to->trailers   = apr_table_clone(pool, from->trailers);
-    }
-    return to;
-}
-
-void h2_response_set_trailers(h2_response *response, apr_table_t *trailers)
-{
-    response->trailers = trailers;
-}
-
diff -up --new-file httpd-2.4.23/modules/http2/h2_response.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_response.h
--- httpd-2.4.23/modules/http2/h2_response.h	2016-03-02 12:21:28.000000000 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_response.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,73 +0,0 @@
-/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __mod_h2__h2_response__
-#define __mod_h2__h2_response__
-
-#include "h2.h"
-
-/**
- * Create the response from the status and parsed header lines.
- * @param stream_id id of the stream to create the response for
- * @param rst_error error for reset or 0
- * @param http_status  http status code of response
- * @param hlines the text lines of the response header
- * @param pool the memory pool to use
- */
-h2_response *h2_response_create(int stream_id,
-                                int rst_error,
-                                int http_status,
-                                apr_array_header_t *hlines,
-                                apr_table_t *notes,
-                                apr_pool_t *pool);
-
-/**
- * Create the response from the given request_rec.
- * @param stream_id id of the stream to create the response for
- * @param r the request record which was processed
- * @param header the headers of the response
- * @param pool the memory pool to use
- */
-h2_response *h2_response_rcreate(int stream_id, request_rec *r,
-                                 apr_table_t *header, apr_pool_t *pool);
-
-/**
- * Create the response for the given error.
- * @param stream_id id of the stream to create the response for
- * @param type the error code
- * @param req the original h2_request
- * @param pool the memory pool to use
- */
-h2_response *h2_response_die(int stream_id, apr_status_t type,
-                             const struct h2_request *req, apr_pool_t *pool);
-
-/**
- * Deep copies the response into a new pool.
- * @param pool the pool to use for the clone
- * @param from the response to clone
- * @return the cloned response
- */
-h2_response *h2_response_clone(apr_pool_t *pool, h2_response *from);
-
-/**
- * Set the trailers in the reponse. Will replace any existing trailers. Will
- * *not* clone the table.
- *
- * @param response the repsone to set the trailers for
- * @param trailers the trailers to set
- */
-void h2_response_set_trailers(h2_response *response, apr_table_t *trailers);
-
-#endif /* defined(__mod_h2__h2_response__) */
diff -up --new-file httpd-2.4.23/modules/http2/h2_session.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_session.c
--- httpd-2.4.23/modules/http2/h2_session.c	2017-12-27 23:01:33.408186020 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_session.c	2017-07-04 14:34:15.000000000 +0200
@@ -27,9 +27,11 @@
 #include <http_log.h>
 #include <scoreboard.h>
 
+#include <mpm_common.h>
+
 #include "h2_private.h"
 #include "h2.h"
-#include "h2_bucket_eoc.h"
+#include "h2_bucket_beam.h"
 #include "h2_bucket_eos.h"
 #include "h2_config.h"
 #include "h2_ctx.h"
@@ -38,9 +40,8 @@
 #include "h2_mplx.h"
 #include "h2_push.h"
 #include "h2_request.h"
-#include "h2_response.h"
+#include "h2_headers.h"
 #include "h2_stream.h"
-#include "h2_from_h1.h"
 #include "h2_task.h"
 #include "h2_session.h"
 #include "h2_util.h"
@@ -48,6 +49,15 @@
 #include "h2_workers.h"
 
 
+static apr_status_t dispatch_master(h2_session *session);
+static apr_status_t h2_session_read(h2_session *session, int block);
+static void transit(h2_session *session, const char *action, 
+                    h2_session_state nstate);
+
+static void on_stream_state_enter(void *ctx, h2_stream *stream);
+static void on_stream_state_event(void *ctx, h2_stream *stream, h2_stream_event_t ev);
+static void on_stream_event(void *ctx, h2_stream *stream, h2_stream_event_t ev);
+
 static int h2_session_status_from_apr_status(apr_status_t rv)
 {
     if (rv == APR_SUCCESS) {
@@ -62,80 +72,44 @@ static int h2_session_status_from_apr_st
     return NGHTTP2_ERR_PROTO;
 }
 
-static void update_window(void *ctx, int stream_id, apr_off_t bytes_read)
+h2_stream *h2_session_stream_get(h2_session *session, int stream_id)
 {
-    h2_session *session = (h2_session*)ctx;
-    nghttp2_session_consume(session->ngh2, stream_id, bytes_read);
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                  "h2_session(%ld-%d): consumed %ld bytes",
-                  session->id, stream_id, (long)bytes_read);
+    return nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
 }
 
-static apr_status_t h2_session_receive(void *ctx, 
-                                       const char *data, apr_size_t len,
-                                       apr_size_t *readlen);
-
-static int is_accepting_streams(h2_session *session); 
 static void dispatch_event(h2_session *session, h2_session_event_t ev, 
                              int err, const char *msg);
 
-apr_status_t h2_session_stream_done(h2_session *session, h2_stream *stream)
+void h2_session_event(h2_session *session, h2_session_event_t ev, 
+                             int err, const char *msg)
 {
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                  "h2_stream(%ld-%d): EOS bucket cleanup -> done", 
-                  session->id, stream->id);
-    h2_ihash_remove(session->streams, stream->id);
-    h2_mplx_stream_done(session->mplx, stream);
-    
-    dispatch_event(session, H2_SESSION_EV_STREAM_DONE, 0, NULL);
-    return APR_SUCCESS;
+    dispatch_event(session, ev, err, msg);
 }
 
-typedef struct stream_sel_ctx {
-    h2_session *session;
-    h2_stream *candidate;
-} stream_sel_ctx;
-
-static int find_cleanup_stream(void *udata, void *sdata)
+static int rst_unprocessed_stream(h2_stream *stream, void *ctx)
 {
-    stream_sel_ctx *ctx = udata;
-    h2_stream *stream = sdata;
-    if (H2_STREAM_CLIENT_INITIATED(stream->id)) {
-        if (!ctx->session->local.accepting
-            && stream->id > ctx->session->local.accepted_max) {
-            ctx->candidate = stream;
-            return 0;
-        }
-    }
-    else {
-        if (!ctx->session->remote.accepting
-            && stream->id > ctx->session->remote.accepted_max) {
-            ctx->candidate = stream;
-            return 0;
-        }
+    int unprocessed = (!h2_stream_was_closed(stream)
+                       && (H2_STREAM_CLIENT_INITIATED(stream->id)? 
+                           (!stream->session->local.accepting
+                            && stream->id > stream->session->local.accepted_max)
+                            : 
+                           (!stream->session->remote.accepting
+                            && stream->id > stream->session->remote.accepted_max))
+                       ); 
+    if (unprocessed) {
+        h2_stream_rst(stream, H2_ERR_NO_ERROR);
+        return 0;
     }
     return 1;
 }
 
-static void cleanup_streams(h2_session *session)
+static void cleanup_unprocessed_streams(h2_session *session)
 {
-    stream_sel_ctx ctx;
-    ctx.session = session;
-    ctx.candidate = NULL;
-    while (1) {
-        h2_ihash_iter(session->streams, find_cleanup_stream, &ctx);
-        if (ctx.candidate) {
-            h2_session_stream_done(session, ctx.candidate);
-            ctx.candidate = NULL;
-        }
-        else {
-            break;
-        }
-    }
+    h2_mplx_stream_do(session->mplx, rst_unprocessed_stream, session);
 }
 
-h2_stream *h2_session_open_stream(h2_session *session, int stream_id,
-                                  int initiated_on, const h2_request *req)
+static h2_stream *h2_session_open_stream(h2_session *session, int stream_id,
+                                         int initiated_on)
 {
     h2_stream * stream;
     apr_pool_t *stream_pool;
@@ -143,26 +117,11 @@ h2_stream *h2_session_open_stream(h2_ses
     apr_pool_create(&stream_pool, session->pool);
     apr_pool_tag(stream_pool, "h2_stream");
     
-    stream = h2_stream_open(stream_id, stream_pool, session, 
-                            initiated_on, req);
-    nghttp2_session_set_stream_user_data(session->ngh2, stream_id, stream);
-    h2_ihash_add(session->streams, stream);
-    
-    if (H2_STREAM_CLIENT_INITIATED(stream_id)) {
-        if (stream_id > session->remote.emitted_max) {
-            ++session->remote.emitted_count;
-            session->remote.emitted_max = stream->id;
-            session->local.accepted_max = stream->id;
-        }
-    }
-    else {
-        if (stream_id > session->local.emitted_max) {
-            ++session->local.emitted_count;
-            session->remote.emitted_max = stream->id;
-        }
+    stream = h2_stream_create(stream_id, stream_pool, session, 
+                              session->monitor, initiated_on);
+    if (stream) {
+        nghttp2_session_set_stream_user_data(session->ngh2, stream_id, stream);
     }
-    dispatch_event(session, H2_SESSION_EV_STREAM_OPEN, 0, NULL);
-    
     return stream;
 }
 
@@ -219,14 +178,6 @@ static int stream_pri_cmp(int sid1, int
     return spri_cmp(sid1, s1, sid2, s2, session);
 }
 
-static apr_status_t stream_schedule(h2_session *session,
-                                    h2_stream *stream, int eos)
-{
-    (void)session;
-    return h2_stream_schedule(stream, eos, h2_session_push_enabled(session), 
-                              stream_pri_cmp, session);
-}
-
 /*
  * Callback when nghttp2 wants to send bytes back to the client.
  */
@@ -236,9 +187,9 @@ static ssize_t send_cb(nghttp2_session *
 {
     h2_session *session = (h2_session *)userp;
     apr_status_t status;
-    
     (void)ngh2;
     (void)flags;
+    
     status = h2_conn_io_write(&session->io, (const char *)data, length);
     if (status == APR_SUCCESS) {
         return length;
@@ -262,96 +213,40 @@ static int on_invalid_frame_recv_cb(nght
         char buffer[256];
         
         h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03063)
-                      "h2_session(%ld): recv invalid FRAME[%s], frames=%ld/%ld (r/s)",
-                      session->id, buffer, (long)session->frames_received,
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                      H2_SSSN_LOG(APLOGNO(03063), session, 
+                      "recv invalid FRAME[%s], frames=%ld/%ld (r/s)"),
+                      buffer, (long)session->frames_received,
                      (long)session->frames_sent);
     }
     return 0;
 }
 
-static h2_stream *get_stream(h2_session *session, int stream_id)
-{
-    return nghttp2_session_get_stream_user_data(session->ngh2, stream_id);
-}
-
 static int on_data_chunk_recv_cb(nghttp2_session *ngh2, uint8_t flags,
                                  int32_t stream_id,
                                  const uint8_t *data, size_t len, void *userp)
 {
     h2_session *session = (h2_session *)userp;
-    apr_status_t status = APR_SUCCESS;
+    apr_status_t status = APR_EINVAL;
     h2_stream * stream;
-    int rv;
+    int rv = 0;
     
-    (void)flags;
-    if (!is_accepting_streams(session)) {
-        /* ignore */
-        return 0;
+    stream = h2_session_stream_get(session, stream_id);
+    if (stream) {
+        status = h2_stream_recv_DATA(stream, flags, data, len);
     }
-    
-    stream = get_stream(session, stream_id);
-    if (!stream) {
+    else {
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03064)
                       "h2_stream(%ld-%d): on_data_chunk for unknown stream",
                       session->id, (int)stream_id);
-        rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
-                                       NGHTTP2_INTERNAL_ERROR);
-        if (nghttp2_is_fatal(rv)) {
-            return NGHTTP2_ERR_CALLBACK_FAILURE;
-        }
-        return 0;
-    }
-
-    /* FIXME: enabling setting EOS this way seems to break input handling
-     * in mod_proxy_http2. why? */
-    status = h2_stream_write_data(stream, (const char *)data, len,
-                                  0 /*flags & NGHTTP2_FLAG_END_STREAM*/);
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
-                  "h2_stream(%ld-%d): data_chunk_recv, written %ld bytes",
-                  session->id, stream_id, (long)len);
-    if (status != APR_SUCCESS) {
-        update_window(session, stream_id, len);
-        rv = nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream_id,
-                                       H2_STREAM_RST(stream, H2_ERR_INTERNAL_ERROR));
-        if (nghttp2_is_fatal(rv)) {
-            return NGHTTP2_ERR_CALLBACK_FAILURE;
-        }
+        rv = NGHTTP2_ERR_CALLBACK_FAILURE;
     }
-    return 0;
-}
-
-static apr_status_t stream_release(h2_session *session, 
-                                   h2_stream *stream,
-                                   uint32_t error_code) 
-{
-    conn_rec *c = session->c;
-    apr_bucket *b;
-    apr_status_t status;
     
-    if (!error_code) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                      "h2_stream(%ld-%d): handled, closing", 
-                      session->id, (int)stream->id);
-        if (H2_STREAM_CLIENT_INITIATED(stream->id)) {
-            if (stream->id > session->local.completed_max) {
-                session->local.completed_max = stream->id;
-            }
-        }
-    }
-    else {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03065)
-                      "h2_stream(%ld-%d): closing with err=%d %s", 
-                      session->id, (int)stream->id, (int)error_code,
-                      h2_h2_err_description(error_code));
-        h2_stream_rst(stream, error_code);
+    if (status != APR_SUCCESS) {
+        /* count this as consumed explicitly as no one will read it */
+        nghttp2_session_consume(session->ngh2, stream_id, len);
     }
-    
-    b = h2_bucket_eos_create(c->bucket_alloc, stream);
-    APR_BRIGADE_INSERT_TAIL(session->bbtmp, b);
-    status = h2_conn_io_pass(&session->io, session->bbtmp);
-    apr_brigade_cleanup(session->bbtmp);
-    return status;
+    return rv;
 }
 
 static int on_stream_close_cb(nghttp2_session *ngh2, int32_t stream_id,
@@ -361,9 +256,15 @@ static int on_stream_close_cb(nghttp2_se
     h2_stream *stream;
     
     (void)ngh2;
-    stream = get_stream(session, stream_id);
+    stream = h2_session_stream_get(session, stream_id);
     if (stream) {
-        stream_release(session, stream, error_code);
+        if (error_code) {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+                          H2_STRM_LOG(APLOGNO(03065), stream, 
+                          "closing with err=%d %s"), 
+                          (int)error_code, h2_h2_err_description(error_code));
+            h2_stream_rst(stream, error_code);
+        }
     }
     return 0;
 }
@@ -377,12 +278,12 @@ static int on_begin_headers_cb(nghttp2_s
     /* We may see HEADERs at the start of a stream or after all DATA
      * streams to carry trailers. */
     (void)ngh2;
-    s = get_stream(session, frame->hd.stream_id);
+    s = h2_session_stream_get(session, frame->hd.stream_id);
     if (s) {
         /* nop */
     }
     else {
-        s = h2_session_open_stream(userp, frame->hd.stream_id, 0, NULL);
+        s = h2_session_open_stream(userp, frame->hd.stream_id, 0);
     }
     return s? 0 : NGHTTP2_ERR_START_STREAM_NOT_ALLOWED;
 }
@@ -398,30 +299,17 @@ static int on_header_cb(nghttp2_session
     apr_status_t status;
     
     (void)flags;
-    if (!is_accepting_streams(session)) {
-        /* just ignore */
-        return 0;
-    }
-    
-    stream = get_stream(session, frame->hd.stream_id);
+    stream = h2_session_stream_get(session, frame->hd.stream_id);
     if (!stream) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
-                      APLOGNO(02920) 
-                      "h2_session:  stream(%ld-%d): on_header unknown stream",
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(02920) 
+                      "h2_stream(%ld-%d): on_header unknown stream",
                       session->id, (int)frame->hd.stream_id);
         return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
     }
     
     status = h2_stream_add_header(stream, (const char *)name, namelen,
                                   (const char *)value, valuelen);
-    if (status == APR_ECONNRESET) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
-                      "h2-stream(%ld-%d): on_header, reset stream",
-                      session->id, stream->id);
-        nghttp2_submit_rst_stream(ngh2, NGHTTP2_FLAG_NONE, stream->id,
-                                  NGHTTP2_INTERNAL_ERROR);
-    }
-    else if (status != APR_SUCCESS && !stream->response) {
+    if (status != APR_SUCCESS && !h2_stream_is_ready(stream)) {
         return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
     }
     return 0;
@@ -437,16 +325,17 @@ static int on_frame_recv_cb(nghttp2_sess
                             void *userp)
 {
     h2_session *session = (h2_session *)userp;
-    apr_status_t status = APR_SUCCESS;
     h2_stream *stream;
+    apr_status_t rv = APR_SUCCESS;
     
     if (APLOGcdebug(session->c)) {
         char buffer[256];
         
         h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03066)
-                      "h2_session(%ld): recv FRAME[%s], frames=%ld/%ld (r/s)",
-                      session->id, buffer, (long)session->frames_received,
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                      H2_SSSN_LOG(APLOGNO(03066), session, 
+                      "recv FRAME[%s], frames=%ld/%ld (r/s)"),
+                      buffer, (long)session->frames_received,
                      (long)session->frames_sent);
     }
 
@@ -456,49 +345,25 @@ static int on_frame_recv_cb(nghttp2_sess
             /* This can be HEADERS for a new stream, defining the request,
              * or HEADER may come after DATA at the end of a stream as in
              * trailers */
-            stream = get_stream(session, frame->hd.stream_id);
+            stream = h2_session_stream_get(session, frame->hd.stream_id);
             if (stream) {
-                int eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
-                
-                if (h2_stream_is_scheduled(stream)) {
-                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
-                                  "h2_stream(%ld-%d): TRAILER, eos=%d", 
-                                  session->id, frame->hd.stream_id, eos);
-                    if (eos) {
-                        status = h2_stream_close_input(stream);
-                    }
-                }
-                else {
-                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
-                                  "h2_stream(%ld-%d): HEADER, eos=%d", 
-                                  session->id, frame->hd.stream_id, eos);
-                    status = stream_schedule(session, stream, eos);
-                }
-            }
-            else {
-                status = APR_EINVAL;
+                rv = h2_stream_recv_frame(stream, NGHTTP2_HEADERS, frame->hd.flags);
             }
             break;
         case NGHTTP2_DATA:
-            stream = get_stream(session, frame->hd.stream_id);
+            stream = h2_session_stream_get(session, frame->hd.stream_id);
             if (stream) {
-                int eos = (frame->hd.flags & NGHTTP2_FLAG_END_STREAM);
-                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
-                              "h2_stream(%ld-%d): DATA, len=%ld, eos=%d", 
-                              session->id, frame->hd.stream_id, 
-                              (long)frame->hd.length, eos);
-                if (eos) {
-                    status = h2_stream_close_input(stream);
-                }
-            }
-            else {
-                status = APR_EINVAL;
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,  
+                              H2_STRM_LOG(APLOGNO(02923), stream, 
+                              "DATA, len=%ld, flags=%d"), 
+                              (long)frame->hd.length, frame->hd.flags);
+                rv = h2_stream_recv_frame(stream, NGHTTP2_DATA, frame->hd.flags);
             }
             break;
         case NGHTTP2_PRIORITY:
             session->reprioritize = 1;
             ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                          "h2_session:  stream(%ld-%d): PRIORITY frame "
+                          "h2_stream(%ld-%d): PRIORITY frame "
                           " weight=%d, dependsOn=%d, exclusive=%d", 
                           session->id, (int)frame->hd.stream_id,
                           frame->priority.pri_spec.weight,
@@ -507,18 +372,17 @@ static int on_frame_recv_cb(nghttp2_sess
             break;
         case NGHTTP2_WINDOW_UPDATE:
             ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                          "h2_session:  stream(%ld-%d): WINDOW_UPDATE "
-                          "incr=%d", 
+                          "h2_stream(%ld-%d): WINDOW_UPDATE incr=%d", 
                           session->id, (int)frame->hd.stream_id,
                           frame->window_update.window_size_increment);
             break;
         case NGHTTP2_RST_STREAM:
             ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03067)
-                          "h2_session(%ld-%d): RST_STREAM by client, errror=%d",
+                          "h2_stream(%ld-%d): RST_STREAM by client, errror=%d",
                           session->id, (int)frame->hd.stream_id,
                           (int)frame->rst_stream.error_code);
-            stream = get_stream(session, frame->hd.stream_id);
-            if (stream && stream->request && stream->request->initiated_on) {
+            stream = h2_session_stream_get(session, frame->hd.stream_id);
+            if (stream && stream->initiated_on) {
                 ++session->pushes_reset;
             }
             else {
@@ -526,9 +390,16 @@ static int on_frame_recv_cb(nghttp2_sess
             }
             break;
         case NGHTTP2_GOAWAY:
-            session->remote.accepted_max = frame->goaway.last_stream_id;
-            session->remote.error = frame->goaway.error_code;
-            dispatch_event(session, H2_SESSION_EV_REMOTE_GOAWAY, 0, NULL);
+            if (frame->goaway.error_code == 0 
+                && frame->goaway.last_stream_id == ((1u << 31) - 1)) {
+                /* shutdown notice. Should not come from a client... */
+                session->remote.accepting = 0;
+            }
+            else {
+                session->remote.accepted_max = frame->goaway.last_stream_id;
+                dispatch_event(session, H2_SESSION_EV_REMOTE_GOAWAY, 
+                               frame->goaway.error_code, NULL);
+            }
             break;
         default:
             if (APLOGctrace2(session->c)) {
@@ -537,27 +408,21 @@ static int on_frame_recv_cb(nghttp2_sess
                 h2_util_frame_print(frame, buffer,
                                     sizeof(buffer)/sizeof(buffer[0]));
                 ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                              "h2_session: on_frame_rcv %s", buffer);
+                              H2_SSSN_MSG(session, "on_frame_rcv %s"), buffer);
             }
             break;
     }
+    return (APR_SUCCESS == rv)? 0 : NGHTTP2_ERR_PROTO;
+}
 
-    if (status != APR_SUCCESS) {
-        int rv;
-        
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
-                      APLOGNO(02923) 
-                      "h2_session: stream(%ld-%d): error handling frame",
-                      session->id, (int)frame->hd.stream_id);
-        rv = nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
-                                       frame->hd.stream_id,
-                                       NGHTTP2_INTERNAL_ERROR);
-        if (nghttp2_is_fatal(rv)) {
-            return NGHTTP2_ERR_CALLBACK_FAILURE;
-        }
+static int h2_session_continue_data(h2_session *session) {
+    if (h2_mplx_has_master_events(session->mplx)) {
+        return 0;
     }
-    
-    return 0;
+    if (h2_conn_io_needs_flush(&session->io)) {
+        return 0;
+    }
+    return 1;
 }
 
 static char immortal_zeros[H2_MAX_PADLEN];
@@ -580,24 +445,28 @@ static int on_send_data_cb(nghttp2_sessi
     
     (void)ngh2;
     (void)source;
+    if (!h2_session_continue_data(session)) {
+        return NGHTTP2_ERR_WOULDBLOCK;
+    }
+
     if (frame->data.padlen > H2_MAX_PADLEN) {
         return NGHTTP2_ERR_PROTO;
     }
     padlen = (unsigned char)frame->data.padlen;
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                  "h2_stream(%ld-%d): send_data_cb for %ld bytes",
-                  session->id, (int)stream_id, (long)length);
-                  
-    stream = get_stream(session, stream_id);
+    stream = h2_session_stream_get(session, stream_id);
     if (!stream) {
         ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_NOTFOUND, session->c,
                       APLOGNO(02924) 
-                      "h2_stream(%ld-%d): send_data, lookup stream",
+                      "h2_stream(%ld-%d): send_data, stream not found",
                       session->id, (int)stream_id);
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
     
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+                  H2_STRM_MSG(stream, "send_data_cb for %ld bytes"),
+                  (long)length);
+                  
     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);
@@ -605,23 +474,22 @@ static int on_send_data_cb(nghttp2_sessi
     
     if (status != APR_SUCCESS) {
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
-                      "h2_stream(%ld-%d): writing frame header",
-                      session->id, (int)stream_id);
+                      H2_STRM_MSG(stream, "writing frame header"));
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
     
     status = h2_stream_read_to(stream, session->bbtmp, &len, &eos);
     if (status != APR_SUCCESS) {
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
-                      "h2_stream(%ld-%d): send_data_cb, reading stream",
-                      session->id, (int)stream_id);
+                      H2_STRM_MSG(stream, "send_data_cb, reading stream"));
+        apr_brigade_cleanup(session->bbtmp);
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
     else if (len != length) {
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
-                      "h2_stream(%ld-%d): send_data_cb, wanted %ld bytes, "
-                      "got %ld from stream",
-                      session->id, (int)stream_id, (long)length, (long)len);
+                      H2_STRM_MSG(stream, "send_data_cb, wanted %ld bytes, "
+                      "got %ld from stream"), (long)length, (long)len);
+        apr_brigade_cleanup(session->bbtmp);
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
     
@@ -632,17 +500,16 @@ static int on_send_data_cb(nghttp2_sessi
     }
     
     status = h2_conn_io_pass(&session->io, session->bbtmp);
-        
     apr_brigade_cleanup(session->bbtmp);
+    
     if (status == APR_SUCCESS) {
-        stream->data_frames_sent++;
+        stream->out_data_frames++;
+        stream->out_data_octets += length;
         return 0;
     }
     else {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
-                      APLOGNO(02925) 
-                      "h2_stream(%ld-%d): failed send_data_cb",
-                      session->id, (int)stream_id);
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,  
+                      H2_STRM_LOG(APLOGNO(02925), stream, "failed send_data_cb"));
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
 }
@@ -652,19 +519,62 @@ static int on_frame_send_cb(nghttp2_sess
                             void *user_data)
 {
     h2_session *session = user_data;
+    h2_stream *stream;
+    int stream_id = frame->hd.stream_id;
+    
+    ++session->frames_sent;
+    switch (frame->hd.type) {
+        case NGHTTP2_PUSH_PROMISE:
+            /* PUSH_PROMISE we report on the promised stream */
+            stream_id = frame->push_promise.promised_stream_id;
+            break;
+        default:    
+            break;
+    }
+    
     if (APLOGcdebug(session->c)) {
         char buffer[256];
         
         h2_util_frame_print(frame, buffer, sizeof(buffer)/sizeof(buffer[0]));
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03068)
-                      "h2_session(%ld): sent FRAME[%s], frames=%ld/%ld (r/s)",
-                      session->id, buffer, (long)session->frames_received,
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                      H2_SSSN_LOG(APLOGNO(03068), session, 
+                      "sent FRAME[%s], frames=%ld/%ld (r/s)"),
+                      buffer, (long)session->frames_received,
                      (long)session->frames_sent);
     }
-    ++session->frames_sent;
+    
+    stream = h2_session_stream_get(session, stream_id);
+    if (stream) {
+        h2_stream_send_frame(stream, frame->hd.type, frame->hd.flags);
+    }
     return 0;
 }
 
+#ifdef H2_NG2_INVALID_HEADER_CB
+static int on_invalid_header_cb(nghttp2_session *ngh2, 
+                                const nghttp2_frame *frame, 
+                                const uint8_t *name, size_t namelen, 
+                                const uint8_t *value, size_t valuelen, 
+                                uint8_t flags, void *user_data)
+{
+    h2_session *session = user_data;
+    h2_stream *stream;
+    
+    if (APLOGcdebug(session->c)) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03456)
+                      "h2_stream(%ld-%d): invalid header '%s: %s'", 
+                      session->id, (int)frame->hd.stream_id,
+                      apr_pstrndup(session->pool, (const char *)name, namelen),
+                      apr_pstrndup(session->pool, (const char *)value, valuelen));
+    }
+    stream = h2_session_stream_get(session, frame->hd.stream_id);
+    if (stream) {
+        h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+    }
+    return 0;
+}
+#endif
+
 #define NGH2_SET_CALLBACK(callbacks, name, fn)\
 nghttp2_session_callbacks_set_##name##_callback(callbacks, fn)
 
@@ -687,38 +597,30 @@ static apr_status_t init_callbacks(conn_
     NGH2_SET_CALLBACK(*pcb, on_header, on_header_cb);
     NGH2_SET_CALLBACK(*pcb, send_data, on_send_data_cb);
     NGH2_SET_CALLBACK(*pcb, on_frame_send, on_frame_send_cb);
-
+#ifdef H2_NG2_INVALID_HEADER_CB
+    NGH2_SET_CALLBACK(*pcb, on_invalid_header, on_invalid_header_cb);
+#endif
     return APR_SUCCESS;
 }
 
-static void h2_session_destroy(h2_session *session)
+static apr_status_t h2_session_shutdown_notice(h2_session *session)
 {
-    AP_DEBUG_ASSERT(session);    
-
-    h2_ihash_clear(session->streams);
-    if (session->mplx) {
-        h2_mplx_set_consumed_cb(session->mplx, NULL, NULL);
-        h2_mplx_release_and_join(session->mplx, session->iowait);
-        session->mplx = NULL;
-    }
-
-    ap_remove_input_filter_byhandle((session->r? session->r->input_filters :
-                                     session->c->input_filters), "H2_IN");
-    if (session->ngh2) {
-        nghttp2_session_del(session->ngh2);
-        session->ngh2 = NULL;
-    }
-    if (session->c) {
-        h2_ctx_clear(session->c);
-    }
-
-    if (APLOGctrace1(session->c)) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
-                      "h2_session(%ld): destroy", session->id);
+    apr_status_t status;
+    
+    ap_assert(session);
+    if (!session->local.accepting) {
+        return APR_SUCCESS;
     }
-    if (session->pool) {
-        apr_pool_destroy(session->pool);
+    
+    nghttp2_submit_shutdown_notice(session->ngh2);
+    session->local.accepting = 0;
+    status = nghttp2_session_send(session->ngh2);
+    if (status == APR_SUCCESS) {
+        status = h2_conn_io_flush(&session->io);
     }
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                  H2_SSSN_LOG(APLOGNO(03457), session, "sent shutdown notice"));
+    return status;
 }
 
 static apr_status_t h2_session_shutdown(h2_session *session, int error, 
@@ -726,7 +628,10 @@ static apr_status_t h2_session_shutdown(
 {
     apr_status_t status = APR_SUCCESS;
     
-    AP_DEBUG_ASSERT(session);
+    ap_assert(session);
+    if (session->local.shutdown) {
+        return APR_SUCCESS;
+    }
     if (!msg && error) {
         msg = nghttp2_strerror(error);
     }
@@ -747,40 +652,33 @@ static apr_status_t h2_session_shutdown(
          * we have, but no longer accept new ones. Report the max stream
          * we have received and discard all new ones. */
     }
-    nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 
-                          session->local.accepted_max, 
-                          error, (uint8_t*)msg, msg? strlen(msg):0);
-    status = nghttp2_session_send(session->ngh2);
-    if (status == APR_SUCCESS) {
-        status = h2_conn_io_flush(&session->io);
-    }
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03069)
-                  "session(%ld): sent GOAWAY, err=%d, msg=%s", 
-                  session->id, error, msg? msg : "");
-    dispatch_event(session, H2_SESSION_EV_LOCAL_GOAWAY, error, msg);
     
-    if (force_close) {
-        h2_mplx_abort(session->mplx);
+    session->local.accepting = 0;
+    session->local.shutdown = 1;
+    if (!session->c->aborted) {
+        nghttp2_submit_goaway(session->ngh2, NGHTTP2_FLAG_NONE, 
+                              session->local.accepted_max, 
+                              error, (uint8_t*)msg, msg? strlen(msg):0);
+        status = nghttp2_session_send(session->ngh2);
+        if (status == APR_SUCCESS) {
+            status = h2_conn_io_flush(&session->io);
+        }
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                      H2_SSSN_LOG(APLOGNO(03069), session, 
+                                  "sent GOAWAY, err=%d, msg=%s"), error, msg? msg : "");
     }
-    
+    dispatch_event(session, H2_SESSION_EV_LOCAL_GOAWAY, error, msg);
     return status;
 }
 
-static apr_status_t session_pool_cleanup(void *data)
+static apr_status_t session_cleanup(h2_session *session, const char *trigger)
 {
-    h2_session *session = data;
-    /* On a controlled connection shutdown, this gets never
-     * called as we deregister and destroy our pool manually.
-     * However when we have an async mpm, and handed it our idle
-     * connection, it will just cleanup once the connection is closed
-     * from the other side (and sometimes even from out side) and
-     * here we arrive then.
-     */
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
-                  "session(%ld): pool_cleanup", session->id);
+    conn_rec *c = session->c;
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                  H2_SSSN_MSG(session, "pool_cleanup"));
     
-    if (session->state != H2_SESSION_ST_DONE 
-        && session->state != H2_SESSION_ST_LOCAL_SHUTDOWN) {
+    if (session->state != H2_SESSION_ST_DONE
+        && session->state != H2_SESSION_ST_INIT) {
         /* Not good. The connection is being torn down and we have
          * not sent a goaway. This is considered a protocol error and
          * the client has to assume that any streams "in flight" may have
@@ -789,199 +687,210 @@ static apr_status_t session_pool_cleanup
          * connection when sending the next request, this has the effect
          * that at least this one request will fail.
          */
-        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, session->c, APLOGNO(03199)
-                      "session(%ld): connection disappeared without proper "
-                      "goodbye, clients will be confused, should not happen", 
-                      session->id);
-    }
-    /* keep us from destroying the pool, since that is already ongoing. */
-    session->pool = NULL;
-    h2_session_destroy(session);
-    return APR_SUCCESS;
-}
-
-static void *session_malloc(size_t size, void *ctx)
-{
-    h2_session *session = ctx;
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, session->c,
-                  "h2_session(%ld): malloc(%ld)",
-                  session->id, (long)size);
-    return malloc(size);
-}
-
-static void session_free(void *p, void *ctx)
-{
-    h2_session *session = ctx;
-
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, session->c,
-                  "h2_session(%ld): free()",
-                  session->id);
-    free(p);
-}
-
-static void *session_calloc(size_t n, size_t size, void *ctx)
-{
-    h2_session *session = ctx;
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c,
+                      H2_SSSN_LOG(APLOGNO(03199), session, 
+                      "connection disappeared without proper "
+                      "goodbye, clients will be confused, should not happen"));
+    }
 
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, session->c,
-                  "h2_session(%ld): calloc(%ld, %ld)",
-                  session->id, (long)n, (long)size);
-    return calloc(n, size);
+    transit(session, trigger, H2_SESSION_ST_CLEANUP);
+    h2_mplx_release_and_join(session->mplx, session->iowait);
+    session->mplx = NULL;
+
+    ap_assert(session->ngh2);
+    nghttp2_session_del(session->ngh2);
+    session->ngh2 = NULL;
+    h2_ctx_clear(c);
+    
+    
+    return APR_SUCCESS;
 }
 
-static void *session_realloc(void *p, size_t size, void *ctx)
+static apr_status_t session_pool_cleanup(void *data)
 {
-    h2_session *session = ctx;
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE6, 0, session->c,
-                  "h2_session(%ld): realloc(%ld)",
-                  session->id, (long)size);
-    return realloc(p, size);
+    conn_rec *c = data;
+    h2_session *session;
+    h2_ctx *ctx = h2_ctx_get(c, 0);
+    
+    if (ctx && (session = h2_ctx_session_get(ctx))) {
+        /* if the session is still there, now is the last chance
+         * to perform cleanup. Normally, cleanup should have happened
+         * earlier in the connection pre_close. Main reason is that
+         * any ongoing requests on slave connections might still access
+         * data which has, at this time, already been freed. An example
+         * is mod_ssl that uses request hooks. */
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c,
+                      H2_SSSN_LOG(APLOGNO(10020), session, 
+                      "session cleanup triggered by pool cleanup. "
+                      "this should have happened earlier already."));
+        return session_cleanup(session, "pool cleanup");
+    }
+    return APR_SUCCESS;
 }
 
-static h2_session *h2_session_create_int(conn_rec *c,
-                                         request_rec *r,
-                                         h2_ctx *ctx, 
-                                         h2_workers *workers)
+static apr_status_t h2_session_create_int(h2_session **psession,
+                                          conn_rec *c,
+                                          request_rec *r,
+                                          h2_ctx *ctx, 
+                                          h2_workers *workers)
 {
     nghttp2_session_callbacks *callbacks = NULL;
     nghttp2_option *options = NULL;
+    apr_allocator_t *allocator;
+    apr_thread_mutex_t *mutex;
     uint32_t n;
-
     apr_pool_t *pool = NULL;
-    apr_status_t status = apr_pool_create(&pool, c->pool);
     h2_session *session;
+    apr_status_t status;
+    int rv;
+
+    *psession = NULL;
+    status = apr_allocator_create(&allocator);
     if (status != APR_SUCCESS) {
-        return NULL;
+        return status;
+    }
+    apr_allocator_max_free_set(allocator, ap_max_mem_free);
+    apr_pool_create_ex(&pool, c->pool, NULL, allocator);
+    if (!pool) {
+        apr_allocator_destroy(allocator);
+        return APR_ENOMEM;
     }
     apr_pool_tag(pool, "h2_session");
-
+    apr_allocator_owner_set(allocator, pool);
+    status = apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, pool);
+    if (status != APR_SUCCESS) {
+        apr_pool_destroy(pool);
+        return APR_ENOMEM;
+    }
+    apr_allocator_mutex_set(allocator, mutex);
+    
     session = apr_pcalloc(pool, sizeof(h2_session));
-    if (session) {
-        int rv;
-        nghttp2_mem *mem;
-        
-        session->id = c->id;
-        session->c = c;
-        session->r = r;
-        session->s = h2_ctx_server_get(ctx);
-        session->pool = pool;
-        session->config = h2_config_sget(session->s);
-        session->workers = workers;
-        
-        session->state = H2_SESSION_ST_INIT;
-        session->local.accepting = 1;
-        session->remote.accepting = 1;
-        
-        apr_pool_pre_cleanup_register(pool, session, session_pool_cleanup);
-        
-        session->max_stream_count = h2_config_geti(session->config, 
-                                                   H2_CONF_MAX_STREAMS);
-        session->max_stream_mem = h2_config_geti(session->config, 
-                                                 H2_CONF_STREAM_MAX_MEM);
-
-        status = apr_thread_cond_create(&session->iowait, session->pool);
-        if (status != APR_SUCCESS) {
-            return NULL;
-        }
-        
-        session->streams = h2_ihash_create(session->pool,
-                                           offsetof(h2_stream, id));
-        session->mplx = h2_mplx_create(c, session->pool, session->config, 
-                                       session->s->timeout, workers);
-        
-        h2_mplx_set_consumed_cb(session->mplx, update_window, session);
-        
-        /* Install the connection input filter that feeds the session */
-        session->cin = h2_filter_cin_create(session->pool, 
-                                            h2_session_receive, session);
-        ap_add_input_filter("H2_IN", session->cin, r, c);
-
-        h2_conn_io_init(&session->io, c, session->config);
-        session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc);
-        
-        status = init_callbacks(c, &callbacks);
-        if (status != APR_SUCCESS) {
-            ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c, APLOGNO(02927) 
-                          "nghttp2: error in init_callbacks");
-            h2_session_destroy(session);
-            return NULL;
-        }
-        
-        rv = nghttp2_option_new(&options);
-        if (rv != 0) {
-            ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
-                          APLOGNO(02928) "nghttp2_option_new: %s", 
-                          nghttp2_strerror(rv));
-            h2_session_destroy(session);
-            return NULL;
-        }
-        nghttp2_option_set_peer_max_concurrent_streams(
-            options, (uint32_t)session->max_stream_count);
-        /* We need to handle window updates ourself, otherwise we
-         * get flooded by nghttp2. */
-        nghttp2_option_set_no_auto_window_update(options, 1);
-        
-        if (APLOGctrace6(c)) {
-            mem = apr_pcalloc(session->pool, sizeof(nghttp2_mem));
-            mem->mem_user_data = session;
-            mem->malloc    = session_malloc;
-            mem->free      = session_free;
-            mem->calloc    = session_calloc;
-            mem->realloc   = session_realloc;
-            
-            rv = nghttp2_session_server_new3(&session->ngh2, callbacks,
-                                             session, options, mem);
-        }
-        else {
-            rv = nghttp2_session_server_new2(&session->ngh2, callbacks,
-                                             session, options);
-        }
-        nghttp2_session_callbacks_del(callbacks);
-        nghttp2_option_del(options);
-        
-        if (rv != 0) {
-            ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
-                          APLOGNO(02929) "nghttp2_session_server_new: %s",
-                          nghttp2_strerror(rv));
-            h2_session_destroy(session);
-            return NULL;
-        }
-         
-        n = h2_config_geti(session->config, H2_CONF_PUSH_DIARY_SIZE);
-        session->push_diary = h2_push_diary_create(session->pool, n);
-        
-        if (APLOGcdebug(c)) {
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, APLOGNO(03200)
-                          "h2_session(%ld) created, max_streams=%d, "
-                          "stream_mem=%d, workers_limit=%d, workers_max=%d, "
-                          "push_diary(type=%d,N=%d)",
-                          session->id, (int)session->max_stream_count, 
-                          (int)session->max_stream_mem,
-                          session->mplx->workers_limit, 
-                          session->mplx->workers_max, 
-                          session->push_diary->dtype, 
-                          (int)session->push_diary->N);
-        }
+    if (!session) {
+        return APR_ENOMEM;
+    }
+    
+    *psession = session;
+    session->id = c->id;
+    session->c = c;
+    session->r = r;
+    session->s = h2_ctx_server_get(ctx);
+    session->pool = pool;
+    session->config = h2_config_sget(session->s);
+    session->workers = workers;
+    
+    session->state = H2_SESSION_ST_INIT;
+    session->local.accepting = 1;
+    session->remote.accepting = 1;
+    
+    session->max_stream_count = h2_config_geti(session->config, 
+                                               H2_CONF_MAX_STREAMS);
+    session->max_stream_mem = h2_config_geti(session->config, 
+                                             H2_CONF_STREAM_MAX_MEM);
+    
+    status = apr_thread_cond_create(&session->iowait, session->pool);
+    if (status != APR_SUCCESS) {
+        apr_pool_destroy(pool);
+        return status;
+    }
+    
+    session->in_pending = h2_iq_create(session->pool, (int)session->max_stream_count);
+    if (session->in_pending == NULL) {
+        apr_pool_destroy(pool);
+        return APR_ENOMEM;
     }
-    return session;
-}
 
-h2_session *h2_session_create(conn_rec *c, h2_ctx *ctx, h2_workers *workers)
-{
-    return h2_session_create_int(c, NULL, ctx, workers);
+    session->in_process = h2_iq_create(session->pool, (int)session->max_stream_count);
+    if (session->in_process == NULL) {
+        apr_pool_destroy(pool);
+        return APR_ENOMEM;
+    }
+    
+    session->monitor = apr_pcalloc(pool, sizeof(h2_stream_monitor));
+    if (session->monitor == NULL) {
+        apr_pool_destroy(pool);
+        return APR_ENOMEM;
+    }
+    session->monitor->ctx = session;
+    session->monitor->on_state_enter = on_stream_state_enter;
+    session->monitor->on_state_event = on_stream_state_event;
+    session->monitor->on_event = on_stream_event;
+    
+    session->mplx = h2_mplx_create(c, session->pool, session->config, 
+                                   workers);
+    
+    /* connection input filter that feeds the session */
+    session->cin = h2_filter_cin_create(session);
+    ap_add_input_filter("H2_IN", session->cin, r, c);
+    
+    h2_conn_io_init(&session->io, c, session->config);
+    session->bbtmp = apr_brigade_create(session->pool, c->bucket_alloc);
+    
+    status = init_callbacks(c, &callbacks);
+    if (status != APR_SUCCESS) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, status, c, APLOGNO(02927) 
+                      "nghttp2: error in init_callbacks");
+        apr_pool_destroy(pool);
+        return status;
+    }
+    
+    rv = nghttp2_option_new(&options);
+    if (rv != 0) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
+                      APLOGNO(02928) "nghttp2_option_new: %s", 
+                      nghttp2_strerror(rv));
+        apr_pool_destroy(pool);
+        return status;
+    }
+    nghttp2_option_set_peer_max_concurrent_streams(
+                                                   options, (uint32_t)session->max_stream_count);
+    /* We need to handle window updates ourself, otherwise we
+     * get flooded by nghttp2. */
+    nghttp2_option_set_no_auto_window_update(options, 1);
+    
+    rv = nghttp2_session_server_new2(&session->ngh2, callbacks,
+                                     session, options);
+    nghttp2_session_callbacks_del(callbacks);
+    nghttp2_option_del(options);
+    
+    if (rv != 0) {
+        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
+                      APLOGNO(02929) "nghttp2_session_server_new: %s",
+                      nghttp2_strerror(rv));
+        apr_pool_destroy(pool);
+        return APR_ENOMEM;
+    }
+    
+    n = h2_config_geti(session->config, H2_CONF_PUSH_DIARY_SIZE);
+    session->push_diary = h2_push_diary_create(session->pool, n);
+    
+    if (APLOGcdebug(c)) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, c, 
+                      H2_SSSN_LOG(APLOGNO(03200), session, 
+                                  "created, max_streams=%d, stream_mem=%d, "
+                                  "workers_limit=%d, workers_max=%d, "
+                                  "push_diary(type=%d,N=%d)"),
+                      (int)session->max_stream_count, 
+                      (int)session->max_stream_mem,
+                      session->mplx->limit_active, 
+                      session->mplx->max_active, 
+                      session->push_diary->dtype, 
+                      (int)session->push_diary->N);
+    }
+    
+    apr_pool_pre_cleanup_register(pool, c, session_pool_cleanup);    
+    return APR_SUCCESS;
 }
 
-h2_session *h2_session_rcreate(request_rec *r, h2_ctx *ctx, h2_workers *workers)
+apr_status_t h2_session_create(h2_session **psession, 
+                               conn_rec *c, h2_ctx *ctx, h2_workers *workers)
 {
-    return h2_session_create_int(r->connection, r, ctx, workers);
+    return h2_session_create_int(psession, c, NULL, ctx, workers);
 }
 
-void h2_session_eoc_callback(h2_session *session)
+apr_status_t h2_session_rcreate(h2_session **psession, 
+                                request_rec *r, h2_ctx *ctx, h2_workers *workers)
 {
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
-                  "session(%ld): cleanup and destroy", session->id);
-    apr_pool_cleanup_kill(session->pool, session, session_pool_cleanup);
-    h2_session_destroy(session);
+    return h2_session_create_int(psession, r->connection, r, ctx, workers);
 }
 
 static apr_status_t h2_session_start(h2_session *session, int *rv)
@@ -991,7 +900,7 @@ static apr_status_t h2_session_start(h2_
     size_t slen;
     int win_size;
     
-    AP_DEBUG_ASSERT(session);
+    ap_assert(session);
     /* Start the conversation by submitting our SETTINGS frame */
     *rv = 0;
     if (session->r) {
@@ -1029,7 +938,7 @@ static apr_status_t h2_session_start(h2_
         }
         
         /* Now we need to auto-open stream 1 for the request we got. */
-        stream = h2_session_open_stream(session, 1, 0, NULL);
+        stream = h2_session_open_stream(session, 1, 0);
         if (!stream) {
             status = APR_EGENERAL;
             ap_log_rerror(APLOG_MARK, APLOG_ERR, status, session->r,
@@ -1038,11 +947,7 @@ static apr_status_t h2_session_start(h2_
             return status;
         }
         
-        status = h2_stream_set_request(stream, session->r);
-        if (status != APR_SUCCESS) {
-            return status;
-        }
-        status = stream_schedule(session, stream, 1);
+        status = h2_stream_set_request_rec(stream, session->r, 1);
         if (status != APR_SUCCESS) {
             return status;
         }
@@ -1059,17 +964,17 @@ static apr_status_t h2_session_start(h2_
         ++slen;
     }
     
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, APLOGNO(03201)
-                  "h2_session(%ld): start, INITIAL_WINDOW_SIZE=%ld, "
-                  "MAX_CONCURRENT_STREAMS=%d", 
-                  session->id, (long)win_size, (int)session->max_stream_count);
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, 
+                  H2_SSSN_LOG(APLOGNO(03201), session, 
+                  "start, INITIAL_WINDOW_SIZE=%ld, MAX_CONCURRENT_STREAMS=%d"), 
+                  (long)win_size, (int)session->max_stream_count);
     *rv = nghttp2_submit_settings(session->ngh2, NGHTTP2_FLAG_NONE,
                                   settings, slen);
     if (*rv != 0) {
         status = APR_EGENERAL;
         ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
-                      APLOGNO(02935) "nghttp2_submit_settings: %s", 
-                      nghttp2_strerror(*rv));
+                      H2_SSSN_LOG(APLOGNO(02935), session, 
+                      "nghttp2_submit_settings: %s"), nghttp2_strerror(*rv));
     }
     else {
         /* use maximum possible value for connection window size. We are only
@@ -1086,7 +991,8 @@ static apr_status_t h2_session_start(h2_
         if (*rv != 0) {
             status = APR_EGENERAL;
             ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
-                          APLOGNO(02970) "nghttp2_submit_window_update: %s", 
+                          H2_SSSN_LOG(APLOGNO(02970), session,
+                          "nghttp2_submit_window_update: %s"), 
                           nghttp2_strerror(*rv));        
         }
     }
@@ -1094,6 +1000,10 @@ static apr_status_t h2_session_start(h2_
     return status;
 }
 
+static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream,  
+                                      h2_headers *headers, apr_off_t len,
+                                      int eos);
+
 static ssize_t stream_data_cb(nghttp2_session *ng2s,
                               int32_t stream_id,
                               uint8_t *buf,
@@ -1107,7 +1017,7 @@ static ssize_t stream_data_cb(nghttp2_se
     int eos = 0;
     apr_status_t status;
     h2_stream *stream;
-    AP_DEBUG_ASSERT(session);
+    ap_assert(session);
     
     /* The session wants to send more DATA for the stream. We need
      * to find out how much of the requested length we can send without
@@ -1119,19 +1029,20 @@ static ssize_t stream_data_cb(nghttp2_se
     (void)ng2s;
     (void)buf;
     (void)source;
-    stream = get_stream(session, stream_id);
+    stream = h2_session_stream_get(session, stream_id);
     if (!stream) {
         ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, session->c,
                       APLOGNO(02937) 
-                      "h2_stream(%ld-%d): data requested but stream not found",
+                      "h2_stream(%ld-%d): data_cb, stream not found",
                       session->id, (int)stream_id);
         return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
-    
-    AP_DEBUG_ASSERT(!h2_stream_is_suspended(stream));
-    
-    status = h2_stream_out_prepare(stream, &nread, &eos);
+
+    status = h2_stream_out_prepare(stream, &nread, &eos, NULL);
     if (nread) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
+                      H2_STRM_MSG(stream, "prepared no_copy, len=%ld, eos=%d"),
+                      (long)nread, eos);
         *data_flags |=  NGHTTP2_DATA_FLAG_NO_COPY;
     }
     
@@ -1140,8 +1051,8 @@ static ssize_t stream_data_cb(nghttp2_se
             break;
             
         case APR_ECONNRESET:
-            return nghttp2_submit_rst_stream(ng2s, NGHTTP2_FLAG_NONE,
-                stream->id, stream->rst_error);
+        case APR_ECONNABORTED:
+            return NGHTTP2_ERR_CALLBACK_FAILURE;
             
         case APR_EAGAIN:
             /* If there is no data available, our session will automatically
@@ -1149,96 +1060,62 @@ static ssize_t stream_data_cb(nghttp2_se
              * it. Remember at our h2_stream that we need to do this.
              */
             nread = 0;
-            h2_mplx_suspend_stream(session->mplx, stream->id);
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03071)
-                          "h2_stream(%ld-%d): suspending",
-                          session->id, (int)stream_id);
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c,
+                          H2_STRM_LOG(APLOGNO(03071), stream, "suspending"));
             return NGHTTP2_ERR_DEFERRED;
             
         default:
             nread = 0;
-            ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c,
-                          APLOGNO(02938) "h2_stream(%ld-%d): reading data",
-                          session->id, (int)stream_id);
+            ap_log_cerror(APLOG_MARK, APLOG_ERR, status, session->c, 
+                          H2_STRM_LOG(APLOGNO(02938), stream, "reading data"));
             return NGHTTP2_ERR_CALLBACK_FAILURE;
     }
     
     if (eos) {
-        apr_table_t *trailers = h2_stream_get_trailers(stream);
-        if (trailers && !apr_is_empty_table(trailers)) {
-            h2_ngheader *nh;
-            int rv;
-            
-            nh = h2_util_ngheader_make(stream->pool, trailers);
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03072)
-                          "h2_stream(%ld-%d): submit %d trailers",
-                          session->id, (int)stream_id,(int) nh->nvlen);
-            rv = nghttp2_submit_trailer(ng2s, stream->id, nh->nv, nh->nvlen);
-            if (rv < 0) {
-                nread = rv;
-            }
-            *data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
-        }
-        
         *data_flags |= NGHTTP2_DATA_FLAG_EOF;
     }
-    
     return (ssize_t)nread;
 }
 
-typedef struct {
-    nghttp2_nv *nv;
-    size_t nvlen;
-    size_t offset;
-} nvctx_t;
-
 struct h2_stream *h2_session_push(h2_session *session, h2_stream *is,
                                   h2_push *push)
 {
-    apr_status_t status;
     h2_stream *stream;
     h2_ngheader *ngh;
-    int nid;
+    apr_status_t status;
+    int nid = 0;
     
-    ngh = h2_util_ngheader_make_req(is->pool, push->req);
-    nid = nghttp2_submit_push_promise(session->ngh2, 0, is->id, 
-                                      ngh->nv, ngh->nvlen, NULL);
-    if (nid <= 0) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03075)
-                      "h2_stream(%ld-%d): submitting push promise fail: %s",
-                      session->id, is->id, nghttp2_strerror(nid));
+    status = h2_req_create_ngheader(&ngh, is->pool, push->req);
+    if (status == APR_SUCCESS) {
+        nid = nghttp2_submit_push_promise(session->ngh2, 0, is->id, 
+                                          ngh->nv, ngh->nvlen, NULL);
+    }
+    if (status != APR_SUCCESS || nid <= 0) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, 
+                      H2_STRM_LOG(APLOGNO(03075), is, 
+                      "submitting push promise fail: %s"), nghttp2_strerror(nid));
         return NULL;
     }
     ++session->pushes_promised;
     
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03076)
-                  "h2_stream(%ld-%d): SERVER_PUSH %d for %s %s on %d",
-                  session->id, is->id, nid,
-                  push->req->method, push->req->path, is->id);
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                  H2_STRM_LOG(APLOGNO(03076), is, "SERVER_PUSH %d for %s %s on %d"),
+                  nid, push->req->method, push->req->path, is->id);
                   
-    stream = h2_session_open_stream(session, nid, is->id, push->req);
-    if (stream) {
-        status = stream_schedule(session, stream, 1);
-        if (status != APR_SUCCESS) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, session->c,
-                          "h2_stream(%ld-%d): scheduling push stream",
-                          session->id, stream->id);
-            stream = NULL;
-        }
-        ++session->unsent_promises;
-    }
-    else {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03077)
-                      "h2_stream(%ld-%d): failed to create stream obj %d",
-                      session->id, is->id, nid);
-    }
-
+    stream = h2_session_open_stream(session, nid, is->id);
     if (!stream) {
-        /* try to tell the client that it should not wait. */
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                      H2_STRM_LOG(APLOGNO(03077), stream, 
+                      "failed to create stream obj %d"), nid);
+        /* kill the push_promise */
         nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, nid,
                                   NGHTTP2_INTERNAL_ERROR);
+        return NULL;
     }
     
+    h2_session_set_prio(session, stream, push->priority);
+    h2_stream_set_request(stream, push->req);
+    ++session->unsent_promises;
     return stream;
 }
 
@@ -1256,19 +1133,23 @@ apr_status_t h2_session_set_prio(h2_sess
 #ifdef H2_NG2_CHANGE_PRIO
     nghttp2_stream *s_grandpa, *s_parent, *s;
     
+    if (prio == NULL) {
+        /* we treat this as a NOP */
+        return APR_SUCCESS;
+    }
     s = nghttp2_session_find_stream(session->ngh2, stream->id);
     if (!s) {
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
-                      "h2_stream(%ld-%d): lookup of nghttp2_stream failed",
-                      session->id, stream->id);
+                      H2_STRM_MSG(stream, "lookup of nghttp2_stream failed"));
         return APR_EINVAL;
     }
     
     s_parent = nghttp2_stream_get_parent(s);
     if (s_parent) {
         nghttp2_priority_spec ps;
-        int id_parent, id_grandpa, w_parent, w, rv = 0;
-        char *ptype = "AFTER";
+        int id_parent, id_grandpa, w_parent, w;
+        int rv = 0;
+        const char *ptype = "AFTER";
         h2_dependency dep = prio->dependency;
         
         id_parent = nghttp2_stream_get_stream_id(s_parent);
@@ -1329,11 +1210,10 @@ apr_status_t h2_session_set_prio(h2_sess
 
 
         rv = nghttp2_session_change_stream_priority(session->ngh2, stream->id, &ps);
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03203)
-                      "h2_stream(%ld-%d): PUSH %s, weight=%d, "
-                      "depends=%d, returned=%d",
-                      session->id, stream->id, ptype, 
-                      ps.weight, ps.stream_id, rv);
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                      ""H2_STRM_LOG(APLOGNO(03203), stream, 
+                      "PUSH %s, weight=%d, depends=%d, returned=%d"),
+                      ptype, ps.weight, ps.stream_id, rv);
         status = (rv < 0)? APR_EGENERAL : APR_SUCCESS;
     }
 #else
@@ -1372,7 +1252,7 @@ static apr_status_t h2_session_send(h2_s
         apr_socket_timeout_set(socket, saved_timeout);
     }
     session->have_written = 1;
-    if (rv != 0) {
+    if (rv != 0 && rv != NGHTTP2_ERR_WOULDBLOCK) {
         if (nghttp2_is_fatal(rv)) {
             dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, rv, nghttp2_strerror(rv));
             return APR_EGENERAL;
@@ -1386,63 +1266,52 @@ static apr_status_t h2_session_send(h2_s
 }
 
 /**
- * A stream was resumed as new output data arrived.
+ * headers for the stream are ready.
  */
-static apr_status_t on_stream_resume(void *ctx, int stream_id)
+static apr_status_t on_stream_headers(h2_session *session, h2_stream *stream,  
+                                      h2_headers *headers, apr_off_t len,
+                                      int eos)
 {
-    h2_session *session = ctx;
-    h2_stream *stream = get_stream(session, stream_id);
     apr_status_t status = APR_SUCCESS;
-    
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
-                  "h2_stream(%ld-%d): on_resume", session->id, stream_id);
-    if (stream) {
-        int rv = nghttp2_session_resume_data(session->ngh2, stream_id);
-        session->have_written = 1;
-        ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
-                      APLOG_ERR : APLOG_DEBUG, 0, session->c,
-                      APLOGNO(02936) 
-                      "h2_stream(%ld-%d): resuming %s",
-                      session->id, stream->id, rv? nghttp2_strerror(rv) : "");
-    }
-    return status;
-}
-
-/**
- * A response for the stream is ready.
- */
-static apr_status_t on_stream_response(void *ctx, int stream_id)
-{
-    h2_session *session = ctx;
-    h2_stream *stream = get_stream(session, stream_id);
-    apr_status_t status = APR_SUCCESS;
-    h2_response *response;
     int rv = 0;
 
-    AP_DEBUG_ASSERT(session);
+    ap_assert(session);
     ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
-                  "h2_stream(%ld-%d): on_response", session->id, stream_id);
-    if (!stream) {
-        return APR_NOTFOUND;
-    }
-    
-    response = h2_stream_get_response(stream);
-    AP_DEBUG_ASSERT(response || stream->rst_error);
-    
-    if (stream->submitted) {
-        rv = NGHTTP2_PROTOCOL_ERROR;
+                  H2_STRM_MSG(stream, "on_headers"));
+    if (headers->status < 100) {
+        h2_stream_rst(stream, headers->status);
+        goto leave;
+    }
+    else if (stream->has_response) {
+        h2_ngheader *nh;
+        
+        status = h2_res_create_ngtrailer(&nh, stream->pool, headers);
+        
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c, 
+                      H2_STRM_LOG(APLOGNO(03072), stream, "submit %d trailers"), 
+                      (int)nh->nvlen);
+        if (status == APR_SUCCESS) {
+            rv = nghttp2_submit_trailer(session->ngh2, stream->id, 
+                                        nh->nv, nh->nvlen);
+        }
+        else {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
+                          H2_STRM_LOG(APLOGNO(10024), stream, "invalid trailers"));
+            h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+        }
+        goto leave;
     }
-    else if (response && response->headers) {
+    else {
         nghttp2_data_provider provider, *pprovider = NULL;
         h2_ngheader *ngh;
-        const h2_priority *prio;
+        const char *note;
         
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03073)
-                      "h2_stream(%ld-%d): submit response %d, REMOTE_WINDOW_SIZE=%u",
-                      session->id, stream->id, response->http_status,
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                      H2_STRM_LOG(APLOGNO(03073), stream, "submit response %d, REMOTE_WINDOW_SIZE=%u"),
+                      headers->status,
                       (unsigned int)nghttp2_session_get_stream_remote_window_size(session->ngh2, stream->id));
         
-        if (response->content_length != 0) {
+        if (!eos || len > 0) {
             memset(&provider, 0, sizeof(provider));
             provider.source.fd = stream->id;
             provider.read_callback = stream_data_cb;
@@ -1451,7 +1320,7 @@ static apr_status_t on_stream_response(v
         
         /* If this stream is not a pushed one itself,
          * and HTTP/2 server push is enabled here,
-         * and the response is in the range 200-299 *),
+         * and the response HTTP status is not sth >= 400,
          * and the remote side has pushing enabled,
          * -> find and perform any pushes on this stream
          *    *before* we submit the stream response itself.
@@ -1459,50 +1328,70 @@ static apr_status_t on_stream_response(v
          *    headers that get pushed right afterwards.
          * 
          * *) the response code is relevant, as we do not want to 
-         *    make pushes on 401 or 403 codes, neiterh on 301/302
-         *    and friends. And if we see a 304, we do not push either
+         *    make pushes on 401 or 403 codes and friends. 
+         *    And if we see a 304, we do not push either
          *    as the client, having this resource in its cache, might
          *    also have the pushed ones as well.
          */
-        if (stream->request && !stream->request->initiated_on
-            && H2_HTTP_2XX(response->http_status)
+        if (!stream->initiated_on
+            && !stream->has_response
+            && stream->request && stream->request->method
+            && !strcmp("GET", stream->request->method)
+            && (headers->status < 400)
+            && (headers->status != 304)
             && h2_session_push_enabled(session)) {
             
-            h2_stream_submit_pushes(stream);
+            h2_stream_submit_pushes(stream, headers);
         }
         
-        prio = h2_stream_get_priority(stream);
-        if (prio) {
-            h2_session_set_prio(session, stream, prio);
-            /* no showstopper if that fails for some reason */
+        if (!stream->pref_priority) {
+            stream->pref_priority = h2_stream_get_priority(stream, headers);
         }
+        h2_session_set_prio(session, stream, stream->pref_priority);
         
-        ngh = h2_util_ngheader_make_res(stream->pool, response->http_status, 
-                                        response->headers);
-        rv = nghttp2_submit_response(session->ngh2, response->stream_id,
-                                     ngh->nv, ngh->nvlen, pprovider);
-    }
-    else {
-        int err = H2_STREAM_RST(stream, H2_ERR_PROTOCOL_ERROR);
-        
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03074)
-                      "h2_stream(%ld-%d): RST_STREAM, err=%d",
-                      session->id, stream->id, err);
+        note = apr_table_get(headers->notes, H2_FILTER_DEBUG_NOTE);
+        if (note && !strcmp("on", note)) {
+            int32_t connFlowIn, connFlowOut;
 
-        rv = nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE,
-                                       stream->id, err);
-    }
-    
-    stream->submitted = 1;
-    session->have_written = 1;
-    
-    if (stream->request && stream->request->initiated_on) {
-        ++session->pushes_submitted;
-    }
-    else {
-        ++session->responses_submitted;
+            connFlowIn = nghttp2_session_get_effective_local_window_size(session->ngh2); 
+            connFlowOut = nghttp2_session_get_remote_window_size(session->ngh2);
+            headers = h2_headers_copy(stream->pool, headers);
+            apr_table_setn(headers->headers, "conn-flow-in", 
+                           apr_itoa(stream->pool, connFlowIn));
+            apr_table_setn(headers->headers, "conn-flow-out", 
+                           apr_itoa(stream->pool, connFlowOut));
+        }
+        
+        if (headers->status == 103 
+            && !h2_config_geti(session->config, H2_CONF_EARLY_HINTS)) {
+            /* suppress sending this to the client, it might have triggered 
+             * pushes and served its purpose nevertheless */
+            rv = 0;
+            goto leave;
+        }
+        
+        status = h2_res_create_ngheader(&ngh, stream->pool, headers);
+        if (status == APR_SUCCESS) {
+            rv = nghttp2_submit_response(session->ngh2, stream->id,
+                                         ngh->nv, ngh->nvlen, pprovider);
+            stream->has_response = h2_headers_are_response(headers);
+            session->have_written = 1;
+            
+            if (stream->initiated_on) {
+                ++session->pushes_submitted;
+            }
+            else {
+                ++session->responses_submitted;
+            }
+        }
+        else {
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, session->c,
+                          H2_STRM_LOG(APLOGNO(10025), stream, "invalid response"));
+            h2_stream_rst(stream, NGHTTP2_PROTOCOL_ERROR);
+        }
     }
     
+leave:
     if (nghttp2_is_fatal(rv)) {
         status = APR_EGENERAL;
         dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, rv, nghttp2_strerror(rv));
@@ -1527,32 +1416,80 @@ static apr_status_t on_stream_response(v
     return status;
 }
 
-static apr_status_t h2_session_receive(void *ctx, const char *data, 
-                                       apr_size_t len, apr_size_t *readlen)
+/**
+ * A stream was resumed as new response/output data arrived.
+ */
+static apr_status_t on_stream_resume(void *ctx, h2_stream *stream)
 {
     h2_session *session = ctx;
-    ssize_t n;
+    apr_status_t status = APR_EAGAIN;
+    int rv;
+    apr_off_t len = 0;
+    int eos = 0;
+    h2_headers *headers;
+    
+    ap_assert(stream);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c, 
+                  H2_STRM_MSG(stream, "on_resume"));
+    
+send_headers:
+    headers = NULL;
+    status = h2_stream_out_prepare(stream, &len, &eos, &headers);
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c, 
+                  H2_STRM_MSG(stream, "prepared len=%ld, eos=%d"), 
+                  (long)len, eos);
+    if (headers) {
+        status = on_stream_headers(session, stream, headers, len, eos);
+        if (status != APR_SUCCESS || stream->rst_error) {
+            return status;
+        }
+        goto send_headers;
+    }
+    else if (status != APR_EAGAIN) {
+        /* we have DATA to send */
+        if (!stream->has_response) {
+            /* but no response */
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                          H2_STRM_LOG(APLOGNO(03466), stream, 
+                          "no response, RST_STREAM"));
+            h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR);
+            return APR_SUCCESS;
+        } 
+        rv = nghttp2_session_resume_data(session->ngh2, stream->id);
+        session->have_written = 1;
+        ap_log_cerror(APLOG_MARK, nghttp2_is_fatal(rv)?
+                      APLOG_ERR : APLOG_DEBUG, 0, session->c,  
+                      H2_STRM_LOG(APLOGNO(02936), stream, "resumed"));
+    }
+    return status;
+}
+
+static void h2_session_in_flush(h2_session *session)
+{
+    int id;
     
-    if (len > 0) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                      "h2_session(%ld): feeding %ld bytes to nghttp2",
-                      session->id, (long)len);
-        n = nghttp2_session_mem_recv(session->ngh2, (const uint8_t *)data, len);
-        if (n < 0) {
-            if (nghttp2_is_fatal((int)n)) {
-                dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, (int)n, nghttp2_strerror(n));
-                return APR_EGENERAL;
+    while ((id = h2_iq_shift(session->in_process)) > 0) {
+        h2_stream *stream = h2_session_stream_get(session, id);
+        if (stream) {
+            ap_assert(!stream->scheduled);
+            if (h2_stream_prep_processing(stream) == APR_SUCCESS) {
+                h2_mplx_process(session->mplx, stream, stream_pri_cmp, session);
+            }
+            else {
+                h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
             }
         }
-        else {
-            *readlen = n;
-            session->io.bytes_read += n;
+    }
+
+    while ((id = h2_iq_shift(session->in_pending)) > 0) {
+        h2_stream *stream = h2_session_stream_get(session, id);
+        if (stream) {
+            h2_stream_flush_input(stream);
         }
     }
-    return APR_SUCCESS;
 }
 
-static apr_status_t h2_session_read(h2_session *session, int block)
+static apr_status_t session_read(h2_session *session, apr_size_t readlen, int block)
 {
     apr_status_t status, rstatus = APR_EAGAIN;
     conn_rec *c = session->c;
@@ -1564,7 +1501,7 @@ static apr_status_t h2_session_read(h2_s
         status = ap_get_brigade(c->input_filters,
                                 session->bbtmp, AP_MODE_READBYTES,
                                 block? APR_BLOCK_READ : APR_NONBLOCK_READ,
-                                APR_BUCKET_BUFF_SIZE);
+                                H2MAX(APR_BUCKET_BUFF_SIZE, readlen));
         /* get rid of any possible data we do not expect to get */
         apr_brigade_cleanup(session->bbtmp); 
 
@@ -1573,7 +1510,7 @@ static apr_status_t h2_session_read(h2_s
                 /* successful read, reset our idle timers */
                 rstatus = APR_SUCCESS;
                 if (block) {
-                    /* successfull blocked read, try unblocked to
+                    /* successful blocked read, try unblocked to
                      * get more. */
                     block = 0;
                 }
@@ -1591,15 +1528,14 @@ static apr_status_t h2_session_read(h2_s
                         || APR_STATUS_IS_EOF(status)
                         || APR_STATUS_IS_EBADF(status)) {
                         /* common status for a client that has left */
-                        ap_log_cerror( APLOG_MARK, APLOG_TRACE1, status, c,
-                                      "h2_session(%ld): input gone", session->id);
+                        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
+                                      H2_SSSN_MSG(session, "input gone"));
                     }
                     else {
                         /* uncommon status, log on INFO so that we see this */
                         ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, c,
-                                      APLOGNO(02950) 
-                                      "h2_session(%ld): error reading, terminating",
-                                      session->id);
+                                      H2_SSSN_LOG(APLOGNO(02950), session, 
+                                      "error reading, terminating"));
                     }
                     return status;
                 }
@@ -1607,51 +1543,23 @@ static apr_status_t h2_session_read(h2_s
                  * status. */
                 return rstatus;
         }
-        if (!is_accepting_streams(session)) {
-            break;
-        }
-        if ((session->io.bytes_read - read_start) > (64*1024)) {
+        if ((session->io.bytes_read - read_start) > readlen) {
             /* read enough in one go, give write a chance */
             ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
-                          "h2_session(%ld): read 64k, returning", session->id);
+                          H2_SSSN_MSG(session, "read enough, returning"));
             break;
         }
     }
     return rstatus;
 }
 
-static int unsubmitted_iter(void *ctx, void *val)
-{
-    h2_stream *stream = val;
-    if (h2_stream_needs_submit(stream)) {
-        *((int *)ctx) = 1;
-        return 0;
-    }
-    return 1;
-}
-
-static int has_unsubmitted_streams(h2_session *session)
-{
-    int has_unsubmitted = 0;
-    h2_ihash_iter(session->streams, unsubmitted_iter, &has_unsubmitted);
-    return has_unsubmitted;
-}
-
-static int suspended_iter(void *ctx, void *val)
-{
-    h2_stream *stream = val;
-    if (h2_stream_is_suspended(stream)) {
-        *((int *)ctx) = 1;
-        return 0;
-    }
-    return 1;
-}
-
-static int has_suspended_streams(h2_session *session)
+static apr_status_t h2_session_read(h2_session *session, int block)
 {
-    int has_suspended = 0;
-    h2_ihash_iter(session->streams, suspended_iter, &has_suspended);
-    return has_suspended;
+    apr_status_t status = session_read(session, session->max_stream_mem
+                                       * H2MAX(2, session->open_streams), 
+                                       block);
+    h2_session_in_flush(session);
+    return status;
 }
 
 static const char *StateNames[] = {
@@ -1660,11 +1568,10 @@ static const char *StateNames[] = {
     "IDLE",      /* H2_SESSION_ST_IDLE */
     "BUSY",      /* H2_SESSION_ST_BUSY */
     "WAIT",      /* H2_SESSION_ST_WAIT */
-    "LSHUTDOWN", /* H2_SESSION_ST_LOCAL_SHUTDOWN */
-    "RSHUTDOWN", /* H2_SESSION_ST_REMOTE_SHUTDOWN */
+    "CLEANUP",   /* H2_SESSION_ST_CLEANUP */
 };
 
-static const char *state_name(h2_session_state state)
+const char *h2_session_state_str(h2_session_state state)
 {
     if (state >= (sizeof(StateNames)/sizeof(StateNames[0]))) {
         return "unknown";
@@ -1672,18 +1579,6 @@ static const char *state_name(h2_session
     return StateNames[state];
 }
 
-static int is_accepting_streams(h2_session *session)
-{
-    switch (session->state) {
-        case H2_SESSION_ST_IDLE:
-        case H2_SESSION_ST_BUSY:
-        case H2_SESSION_ST_WAIT:
-            return 1;
-        default:
-            return 0;
-    }
-}
-
 static void update_child_status(h2_session *session, int status, const char *msg)
 {
     /* Assume that we also change code/msg when something really happened and
@@ -1705,9 +1600,16 @@ static void update_child_status(h2_sessi
 static void transit(h2_session *session, const char *action, h2_session_state nstate)
 {
     if (session->state != nstate) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03078)
-                      "h2_session(%ld): transit [%s] -- %s --> [%s]", session->id,
-                      state_name(session->state), action, state_name(nstate));
+        int loglvl = APLOG_DEBUG;
+        if ((session->state == H2_SESSION_ST_BUSY && nstate == H2_SESSION_ST_WAIT)
+            || (session->state == H2_SESSION_ST_WAIT && nstate == H2_SESSION_ST_BUSY)){
+            loglvl = APLOG_TRACE1;
+        }
+        ap_log_cerror(APLOG_MARK, loglvl, 0, session->c, 
+                      H2_SSSN_LOG(APLOGNO(03078), session, 
+                      "transit [%s] -- %s --> [%s]"), 
+                      h2_session_state_str(session->state), action, 
+                      h2_session_state_str(nstate));
         session->state = nstate;
         switch (session->state) {
             case H2_SESSION_ST_IDLE:
@@ -1715,12 +1617,6 @@ static void transit(h2_session *session,
                                               SERVER_BUSY_KEEPALIVE
                                               : SERVER_BUSY_READ), "idle");
                 break;
-            case H2_SESSION_ST_REMOTE_SHUTDOWN:
-                update_child_status(session, SERVER_CLOSING, "remote goaway");
-                break;
-            case H2_SESSION_ST_LOCAL_SHUTDOWN:
-                update_child_status(session, SERVER_CLOSING, "local goaway");
-                break;
             case H2_SESSION_ST_DONE:
                 update_child_status(session, SERVER_CLOSING, "done");
                 break;
@@ -1745,39 +1641,22 @@ static void h2_session_ev_init(h2_sessio
 
 static void h2_session_ev_local_goaway(h2_session *session, int arg, const char *msg)
 {
-    session->local.accepting = 0;
-    cleanup_streams(session);
-    switch (session->state) {
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
-            /* already did that? */
-            break;
-        case H2_SESSION_ST_IDLE:
-        case H2_SESSION_ST_REMOTE_SHUTDOWN:
-            /* all done */
-            transit(session, "local goaway", H2_SESSION_ST_DONE);
-            break;
-        default:
-            transit(session, "local goaway", H2_SESSION_ST_LOCAL_SHUTDOWN);
-            break;
+    cleanup_unprocessed_streams(session);
+    if (!session->remote.shutdown) {
+        update_child_status(session, SERVER_CLOSING, "local goaway");
     }
+    transit(session, "local goaway", H2_SESSION_ST_DONE);
 }
 
 static void h2_session_ev_remote_goaway(h2_session *session, int arg, const char *msg)
 {
-    session->remote.accepting = 0;
-    cleanup_streams(session);
-    switch (session->state) {
-        case H2_SESSION_ST_REMOTE_SHUTDOWN:
-            /* already received that? */
-            break;
-        case H2_SESSION_ST_IDLE:
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
-            /* all done */
-            transit(session, "remote goaway", H2_SESSION_ST_DONE);
-            break;
-        default:
-            transit(session, "remote goaway", H2_SESSION_ST_REMOTE_SHUTDOWN);
-            break;
+    if (!session->remote.shutdown) {
+        session->remote.error = arg;
+        session->remote.accepting = 0;
+        session->remote.shutdown = 1;
+        cleanup_unprocessed_streams(session);
+        update_child_status(session, SERVER_CLOSING, "remote goaway");
+        transit(session, "remote goaway", H2_SESSION_ST_DONE);
     }
 }
 
@@ -1786,14 +1665,14 @@ static void h2_session_ev_conn_error(h2_
     switch (session->state) {
         case H2_SESSION_ST_INIT:
         case H2_SESSION_ST_DONE:
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
             /* just leave */
             transit(session, "conn error", H2_SESSION_ST_DONE);
             break;
         
         default:
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03401)
-                          "h2_session(%ld): conn error -> shutdown", session->id);
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                          H2_SSSN_LOG(APLOGNO(03401), session, 
+                          "conn error -> shutdown"));
             h2_session_shutdown(session, arg, msg, 0);
             break;
     }
@@ -1801,31 +1680,19 @@ static void h2_session_ev_conn_error(h2_
 
 static void h2_session_ev_proto_error(h2_session *session, int arg, const char *msg)
 {
-    switch (session->state) {
-        case H2_SESSION_ST_DONE:
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
-            /* just leave */
-            transit(session, "proto error", H2_SESSION_ST_DONE);
-            break;
-        
-        default:
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03402)
-                          "h2_session(%ld): proto error -> shutdown", session->id);
-            h2_session_shutdown(session, arg, msg, 0);
-            break;
+    if (!session->local.shutdown) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                      H2_SSSN_LOG(APLOGNO(03402), session, 
+                      "proto error -> shutdown"));
+        h2_session_shutdown(session, arg, msg, 0);
     }
 }
 
 static void h2_session_ev_conn_timeout(h2_session *session, int arg, const char *msg)
 {
-    switch (session->state) {
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
-            transit(session, "conn timeout", H2_SESSION_ST_DONE);
-            break;
-        default:
-            h2_session_shutdown(session, arg, msg, 1);
-            transit(session, "conn timeout", H2_SESSION_ST_DONE);
-            break;
+    transit(session, msg, H2_SESSION_ST_DONE);
+    if (!session->local.shutdown) {
+        h2_session_shutdown(session, arg, msg, 1);
     }
 }
 
@@ -1833,8 +1700,6 @@ static void h2_session_ev_no_io(h2_sessi
 {
     switch (session->state) {
         case H2_SESSION_ST_BUSY:
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
-        case H2_SESSION_ST_REMOTE_SHUTDOWN:
             /* Nothing to READ, nothing to WRITE on the master connection.
              * Possible causes:
              * - we wait for the client to send us sth
@@ -1842,11 +1707,11 @@ static void h2_session_ev_no_io(h2_sessi
              * - we have finished all streams and the client has sent GO_AWAY
              */
             ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
-                          "h2_session(%ld): NO_IO event, %d streams open", 
-                          session->id, session->open_streams);
+                          H2_SSSN_MSG(session, "NO_IO event, %d streams open"), 
+                          session->open_streams);
+            h2_conn_io_flush(&session->io);
             if (session->open_streams > 0) {
-                if (has_unsubmitted_streams(session) 
-                    || has_suspended_streams(session)) {
+                if (h2_mplx_awaits_data(session->mplx)) {
                     /* waiting for at least one stream to produce data */
                     transit(session, "no io", H2_SESSION_ST_WAIT);
                 }
@@ -1867,7 +1732,7 @@ static void h2_session_ev_no_io(h2_sessi
                     }
                 }
             }
-            else if (is_accepting_streams(session)) {
+            else if (session->local.accepting) {
                 /* When we have no streams, but accept new, switch to idle */
                 apr_time_t now = apr_time_now();
                 transit(session, "no io (keepalive)", H2_SESSION_ST_IDLE);
@@ -1889,18 +1754,6 @@ static void h2_session_ev_no_io(h2_sessi
     }
 }
 
-static void h2_session_ev_stream_ready(h2_session *session, int arg, const char *msg)
-{
-    switch (session->state) {
-        case H2_SESSION_ST_WAIT:
-            transit(session, "stream ready", H2_SESSION_ST_BUSY);
-            break;
-        default:
-            /* nop */
-            break;
-    }
-}
-
 static void h2_session_ev_data_read(h2_session *session, int arg, const char *msg)
 {
     switch (session->state) {
@@ -1930,35 +1783,26 @@ static void h2_session_ev_mpm_stopping(h
 {
     switch (session->state) {
         case H2_SESSION_ST_DONE:
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
             /* nop */
             break;
         default:
-            h2_session_shutdown(session, arg, msg, 0);
+            h2_session_shutdown_notice(session);
             break;
     }
 }
 
 static void h2_session_ev_pre_close(h2_session *session, int arg, const char *msg)
 {
-    switch (session->state) {
-        case H2_SESSION_ST_DONE:
-        case H2_SESSION_ST_LOCAL_SHUTDOWN:
-            /* nop */
-            break;
-        default:
-            h2_session_shutdown(session, arg, msg, 1);
-            break;
-    }
+    h2_session_shutdown(session, arg, msg, 1);
 }
 
-static void h2_session_ev_stream_open(h2_session *session, int arg, const char *msg)
+static void ev_stream_open(h2_session *session, h2_stream *stream)
 {
-    ++session->open_streams;
+    h2_iq_append(session->in_process, stream->id);
     switch (session->state) {
         case H2_SESSION_ST_IDLE:
             if (session->open_streams == 1) {
-                /* enter tiomeout, since we have a stream again */
+                /* enter timeout, since we have a stream again */
                 session->idle_until = (session->s->timeout + apr_time_now());
             }
             break;
@@ -1967,9 +1811,14 @@ static void h2_session_ev_stream_open(h2
     }
 }
 
-static void h2_session_ev_stream_done(h2_session *session, int arg, const char *msg)
+static void ev_stream_closed(h2_session *session, h2_stream *stream)
 {
-    --session->open_streams;
+    apr_bucket *b;
+    
+    if (H2_STREAM_CLIENT_INITIATED(stream->id)
+        && (stream->id > session->local.completed_max)) {
+        session->local.completed_max = stream->id;
+    }
     switch (session->state) {
         case H2_SESSION_ST_IDLE:
             if (session->open_streams == 0) {
@@ -1981,6 +1830,90 @@ static void h2_session_ev_stream_done(h2
         default:
             break;
     }
+    
+    /* The stream might have data in the buffers of the main connection.
+     * We can only free the allocated resources once all had been written.
+     * Send a special buckets on the connection that gets destroyed when
+     * all preceding data has been handled. On its destruction, it is safe
+     * to purge all resources of the stream. */
+    b = h2_bucket_eos_create(session->c->bucket_alloc, stream);
+    APR_BRIGADE_INSERT_TAIL(session->bbtmp, b);
+    h2_conn_io_pass(&session->io, session->bbtmp);
+    apr_brigade_cleanup(session->bbtmp);
+}
+
+static void on_stream_state_enter(void *ctx, h2_stream *stream)
+{
+    h2_session *session = ctx;
+    /* stream entered a new state */
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+                  H2_STRM_MSG(stream, "entered state"));
+    switch (stream->state) {
+        case H2_SS_IDLE: /* stream was created */
+            ++session->open_streams;
+            if (H2_STREAM_CLIENT_INITIATED(stream->id)) {
+                ++session->remote.emitted_count;
+                if (stream->id > session->remote.emitted_max) {
+                    session->remote.emitted_max = stream->id;
+                    session->local.accepted_max = stream->id;
+                }
+            }
+            else {
+                if (stream->id > session->local.emitted_max) {
+                    ++session->local.emitted_count;
+                    session->remote.emitted_max = stream->id;
+                }
+            }
+            break;
+        case H2_SS_OPEN: /* stream has request headers */
+        case H2_SS_RSVD_L: /* stream has request headers */
+            ev_stream_open(session, stream);
+            break;
+        case H2_SS_CLOSED_L: /* stream output was closed */
+            break;
+        case H2_SS_CLOSED_R: /* stream input was closed */
+            break;
+        case H2_SS_CLOSED: /* stream in+out were closed */
+            --session->open_streams;
+            ev_stream_closed(session, stream);
+            break;
+        case H2_SS_CLEANUP:
+            h2_mplx_stream_cleanup(session->mplx, stream);
+            break;
+        default:
+            break;
+    }
+}
+
+static void on_stream_event(void *ctx, h2_stream *stream, 
+                                  h2_stream_event_t ev)
+{
+    h2_session *session = ctx;
+    switch (ev) {
+        case H2_SEV_IN_DATA_PENDING:
+            h2_iq_append(session->in_pending, stream->id);
+            break;
+        default:
+            /* NOP */
+            break;
+    }
+}
+
+static void on_stream_state_event(void *ctx, h2_stream *stream, 
+                                  h2_stream_event_t ev)
+{
+    h2_session *session = ctx;
+    switch (ev) {
+        case H2_SEV_CANCELLED:
+            if (session->state != H2_SESSION_ST_DONE) {
+                nghttp2_submit_rst_stream(session->ngh2, NGHTTP2_FLAG_NONE, 
+                                          stream->id, stream->rst_error);
+            }
+            break;
+        default:
+            /* NOP */
+            break;
+    }
 }
 
 static void dispatch_event(h2_session *session, h2_session_event_t ev, 
@@ -2008,9 +1941,6 @@ static void dispatch_event(h2_session *s
         case H2_SESSION_EV_NO_IO:
             h2_session_ev_no_io(session, arg, msg);
             break;
-        case H2_SESSION_EV_STREAM_READY:
-            h2_session_ev_stream_ready(session, arg, msg);
-            break;
         case H2_SESSION_EV_DATA_READ:
             h2_session_ev_data_read(session, arg, msg);
             break;
@@ -2023,22 +1953,30 @@ static void dispatch_event(h2_session *s
         case H2_SESSION_EV_PRE_CLOSE:
             h2_session_ev_pre_close(session, arg, msg);
             break;
-        case H2_SESSION_EV_STREAM_OPEN:
-            h2_session_ev_stream_open(session, arg, msg);
-            break;
-        case H2_SESSION_EV_STREAM_DONE:
-            h2_session_ev_stream_done(session, arg, msg);
-            break;
         default:
             ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
-                          "h2_session(%ld): unknown event %d", 
-                          session->id, ev);
+                          H2_SSSN_MSG(session, "unknown event %d"), ev);
             break;
     }
+}
+
+/* trigger window updates, stream resumes and submits */
+static apr_status_t dispatch_master(h2_session *session) {
+    apr_status_t status;
     
-    if (session->state == H2_SESSION_ST_DONE) {
-        h2_mplx_abort(session->mplx);
+    status = h2_mplx_dispatch_master_events(session->mplx, 
+                                            on_stream_resume, session);
+    if (status == APR_EAGAIN) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c,
+                      H2_SSSN_MSG(session, "no master event available"));
+    }
+    else if (status != APR_SUCCESS) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, session->c,
+                      H2_SSSN_MSG(session, "dispatch error"));
+        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 
+                       H2_ERR_INTERNAL_ERROR, "dispatch error");
     }
+    return status;
 }
 
 static const int MAX_WAIT_MICROS = 200 * 1000;
@@ -2051,22 +1989,16 @@ apr_status_t h2_session_process(h2_sessi
 
     if (trace) {
         ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
-                      "h2_session(%ld): process start, async=%d", 
-                      session->id, async);
+                      H2_SSSN_MSG(session, "process start, async=%d"), async);
     }
                   
-    if (c->cs) {
-        c->cs->state = CONN_STATE_WRITE_COMPLETION;
-    }
-    
-    while (1) {
-        trace = APLOGctrace3(c);
+    while (session->state != H2_SESSION_ST_DONE) {
         session->have_read = session->have_written = 0;
 
-        if (!ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
+        if (session->local.accepting 
+            && !ap_mpm_query(AP_MPMQ_MPM_STATE, &mpm_state)) {
             if (mpm_state == AP_MPMQ_STOPPING) {
                 dispatch_event(session, H2_SESSION_EV_MPM_STOPPING, 0, NULL);
-                break;
             }
         }
         
@@ -2076,51 +2008,56 @@ apr_status_t h2_session_process(h2_sessi
             case H2_SESSION_ST_INIT:
                 ap_update_child_status_from_conn(c->sbh, SERVER_BUSY_READ, c);
                 if (!h2_is_acceptable_connection(c, 1)) {
-                    update_child_status(session, SERVER_BUSY_READ, "inadequate security");
-                    h2_session_shutdown(session, NGHTTP2_INADEQUATE_SECURITY, NULL, 1);
+                    update_child_status(session, SERVER_BUSY_READ, 
+                                        "inadequate security");
+                    h2_session_shutdown(session, 
+                                        NGHTTP2_INADEQUATE_SECURITY, NULL, 1);
                 } 
                 else {
                     update_child_status(session, SERVER_BUSY_READ, "init");
                     status = h2_session_start(session, &rv);
-                    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, APLOGNO(03079)
-                                  "h2_session(%ld): started on %s:%d", session->id,
+                    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c, 
+                                  H2_SSSN_LOG(APLOGNO(03079), session, 
+                                  "started on %s:%d"), 
                                   session->s->server_hostname,
                                   c->local_addr->port);
                     if (status != APR_SUCCESS) {
-                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+                        dispatch_event(session, 
+                                       H2_SESSION_EV_CONN_ERROR, 0, NULL);
                     }
                     dispatch_event(session, H2_SESSION_EV_INIT, 0, NULL);
                 }
                 break;
                 
             case H2_SESSION_ST_IDLE:
-                /* make certain, we send everything before we idle */
+                /* We trust our connection into the default timeout/keepalive
+                 * handling of the core filters/mpm iff:
+                 * - keep_sync_until is not set
+                 * - we have an async mpm
+                 * - we have no open streams to process
+                 * - we are not sitting on a Upgrade: request
+                 * - we already have seen at least one request
+                 */
                 if (!session->keep_sync_until && async && !session->open_streams
                     && !session->r && session->remote.emitted_count) {
                     if (trace) {
-                        ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
-                                      "h2_session(%ld): async idle, nonblock read, "
-                                      "%d streams open", session->id, 
+                        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
+                                      H2_SSSN_MSG(session, 
+                                      "nonblock read, %d streams open"), 
                                       session->open_streams);
                     }
-                    /* We do not return to the async mpm immediately, since under
-                     * load, mpms show the tendency to throw keep_alive connections
-                     * away very rapidly.
-                     * So, if we are still processing streams, we wait for the
-                     * normal timeout first and, on timeout, close.
-                     * If we have no streams, we still wait a short amount of
-                     * time here for the next frame to arrive, before handing
-                     * it to keep_alive processing of the mpm.
-                     */
+                    h2_conn_io_flush(&session->io);
                     status = h2_session_read(session, 0);
                     
                     if (status == APR_SUCCESS) {
                         session->have_read = 1;
                         dispatch_event(session, H2_SESSION_EV_DATA_READ, 0, NULL);
                     }
-                    else if (APR_STATUS_IS_EAGAIN(status) || APR_STATUS_IS_TIMEUP(status)) {
+                    else if (APR_STATUS_IS_EAGAIN(status) 
+                        || APR_STATUS_IS_TIMEUP(status)) {
                         if (apr_time_now() > session->idle_until) {
-                            dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, 0, NULL);
+                            dispatch_event(session, 
+                                           H2_SESSION_EV_CONN_TIMEOUT, 0, NULL);
                         }
                         else {
                             status = APR_EAGAIN;
@@ -2128,25 +2065,31 @@ apr_status_t h2_session_process(h2_sessi
                         }
                     }
                     else {
-                        ap_log_cerror( APLOG_MARK, APLOG_DEBUG, status, c,
-				      APLOGNO(03403)
-                                      "h2_session(%ld): idle, no data, error", 
-                                      session->id);
-                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, "timeout");
+                        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, c,
+                                      H2_SSSN_LOG(APLOGNO(03403), session, 
+                                      "no data, error"));
+                        dispatch_event(session, 
+                                       H2_SESSION_EV_CONN_ERROR, 0, "timeout");
                     }
                 }
                 else {
+                    /* make certain, we send everything before we idle */
+                    h2_conn_io_flush(&session->io);
                     if (trace) {
-                        ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
-                                      "h2_session(%ld): sync idle, stutter 1-sec, "
-                                      "%d streams open", session->id,
+                        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
+                                      H2_SSSN_MSG(session, 
+                                      "sync, stutter 1-sec, %d streams open"), 
                                       session->open_streams);
                     }
                     /* We wait in smaller increments, using a 1 second timeout.
                      * That gives us the chance to check for MPMQ_STOPPING often. 
                      */
                     status = h2_mplx_idle(session->mplx);
-                    if (status != APR_SUCCESS) {
+                    if (status == APR_EAGAIN) {
+                        dispatch_event(session, H2_SESSION_EV_DATA_READ, 0, NULL);
+                        break;
+                    }
+                    else if (status != APR_SUCCESS) {
                         dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 
                                        H2_ERR_ENHANCE_YOUR_CALM, "less is more");
                     }
@@ -2168,34 +2111,39 @@ apr_status_t h2_session_process(h2_sessi
                         }
                         if (now > session->idle_until) {
                             if (trace) {
-                                ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
-                                              "h2_session(%ld): keepalive timeout",
-                                              session->id);
+                                ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
+                                              H2_SSSN_MSG(session, 
+                                              "keepalive timeout"));
                             }
-                            dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, 0, "timeout");
+                            dispatch_event(session, 
+                                           H2_SESSION_EV_CONN_TIMEOUT, 0, "timeout");
                         }
                         else if (trace) {                        
-                            ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
-                                          "h2_session(%ld): keepalive, %f sec left",
-                                          session->id, (session->idle_until - now) / 1000000.0f);
+                            ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
+                                          H2_SSSN_MSG(session, 
+                                          "keepalive, %f sec left"),
+                                          (session->idle_until - now) / 1000000.0f);
                         }
                         /* continue reading handling */
                     }
+                    else if (APR_STATUS_IS_ECONNABORTED(status)
+                             || APR_STATUS_IS_ECONNRESET(status)
+                             || APR_STATUS_IS_EOF(status)
+                             || APR_STATUS_IS_EBADF(status)) {
+                        ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
+                                      H2_SSSN_MSG(session, "input gone"));
+                        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
+                    }
                     else {
-                        if (trace) {
-                            ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
-                                          "h2_session(%ld): idle(1 sec timeout) "
-                                          "read failed", session->id);
-                        }
+                        ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
+                                      H2_SSSN_MSG(session, 
+                                      "(1 sec timeout) read failed"));
                         dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, "error");
                     }
                 }
-                
                 break;
                 
             case H2_SESSION_ST_BUSY:
-            case H2_SESSION_ST_LOCAL_SHUTDOWN:
-            case H2_SESSION_ST_REMOTE_SHUTDOWN:
                 if (nghttp2_session_want_read(session->ngh2)) {
                     ap_update_child_status(session->c->sbh, SERVER_BUSY_READ, NULL);
                     h2_filter_cin_timeout_set(session->cin, session->s->timeout);
@@ -2215,25 +2163,18 @@ apr_status_t h2_session_process(h2_sessi
                         dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
                     }
                 }
-                
-                /* trigger window updates, stream resumes and submits */
-                status = h2_mplx_dispatch_master_events(session->mplx, 
-                                                        on_stream_resume,
-                                                        on_stream_response, 
-                                                        session);
-                if (status != APR_SUCCESS) {
-                    ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, c,
-                                  "h2_session(%ld): dispatch error", 
-                                  session->id);
-                    dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 
-                                   H2_ERR_INTERNAL_ERROR, 
-                                   "dispatch error");
+
+                status = dispatch_master(session);
+                if (status != APR_SUCCESS && status != APR_EAGAIN) {
                     break;
                 }
                 
                 if (nghttp2_session_want_write(session->ngh2)) {
                     ap_update_child_status(session->c->sbh, SERVER_BUSY_WRITE, NULL);
                     status = h2_session_send(session);
+                    if (status == APR_SUCCESS) {
+                        status = h2_conn_io_flush(&session->io);
+                    }
                     if (status != APR_SUCCESS) {
                         dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 
                                        H2_ERR_INTERNAL_ERROR, "writing");
@@ -2254,21 +2195,11 @@ apr_status_t h2_session_process(h2_sessi
             case H2_SESSION_ST_WAIT:
                 if (session->wait_us <= 0) {
                     session->wait_us = 10;
-                    session->start_wait = apr_time_now();
                     if (h2_conn_io_flush(&session->io) != APR_SUCCESS) {
                         dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
                         break;
                     }
                 }
-                else if ((apr_time_now() - session->start_wait) >= session->s->timeout) {
-                    /* waited long enough */
-                    if (trace) {
-                        ap_log_cerror(APLOG_MARK, APLOG_TRACE3, APR_TIMEUP, c,
-                                      "h2_session: wait for data");
-                    }
-                    dispatch_event(session, H2_SESSION_EV_CONN_TIMEOUT, 0, "timeout");
-                    break;
-                }
                 else {
                     /* repeating, increase timer for graceful backoff */
                     session->wait_us = H2MIN(session->wait_us*2, MAX_WAIT_MICROS);
@@ -2287,8 +2218,8 @@ apr_status_t h2_session_process(h2_sessi
                 }
                 else if (APR_STATUS_IS_TIMEUP(status)) {
                     /* go back to checking all inputs again */
-                    transit(session, "wait cycle", session->local.accepting? 
-                            H2_SESSION_ST_BUSY : H2_SESSION_ST_LOCAL_SHUTDOWN);
+                    transit(session, "wait cycle", session->local.shutdown? 
+                            H2_SESSION_ST_DONE : H2_SESSION_ST_BUSY);
                 }
                 else if (APR_STATUS_IS_ECONNRESET(status) 
                          || APR_STATUS_IS_ECONNABORTED(status)) {
@@ -2296,22 +2227,17 @@ apr_status_t h2_session_process(h2_sessi
                 }
                 else {
                     ap_log_cerror(APLOG_MARK, APLOG_WARNING, status, c,
-				  APLOGNO(03404)
-                                  "h2_session(%ld): waiting on conditional",
-                                  session->id);
+                                  H2_SSSN_LOG(APLOGNO(03404), session, 
+                                  "waiting on conditional"));
                     h2_session_shutdown(session, H2_ERR_INTERNAL_ERROR, 
                                         "cond wait error", 0);
                 }
                 break;
                 
-            case H2_SESSION_ST_DONE:
-                status = APR_EOF;
-                goto out;
-                
             default:
                 ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_EGENERAL, c,
-                              APLOGNO(03080)
-                              "h2_session(%ld): unknown state %d", session->id, session->state);
+                              H2_SSSN_LOG(APLOGNO(03080), session, 
+                              "unknown state"));
                 dispatch_event(session, H2_SESSION_EV_PROTO_ERROR, 0, NULL);
                 break;
         }
@@ -2329,32 +2255,34 @@ apr_status_t h2_session_process(h2_sessi
 out:
     if (trace) {
         ap_log_cerror( APLOG_MARK, APLOG_TRACE3, status, c,
-                      "h2_session(%ld): [%s] process returns", 
-                      session->id, state_name(session->state));
+                      H2_SSSN_MSG(session, "process returns")); 
     }
     
     if ((session->state != H2_SESSION_ST_DONE)
         && (APR_STATUS_IS_EOF(status)
             || APR_STATUS_IS_ECONNRESET(status) 
             || APR_STATUS_IS_ECONNABORTED(status))) {
-            dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
-        }
-
-    status = (session->state == H2_SESSION_ST_DONE)? APR_EOF : APR_SUCCESS;
-    if (session->state == H2_SESSION_ST_DONE) {
-        if (!session->eoc_written) {
-            session->eoc_written = 1;
-            h2_conn_io_write_eoc(&session->io, session);
-        }
+        dispatch_event(session, H2_SESSION_EV_CONN_ERROR, 0, NULL);
     }
-    
-    return status;
+
+    return (session->state == H2_SESSION_ST_DONE)? APR_EOF : APR_SUCCESS;
 }
 
 apr_status_t h2_session_pre_close(h2_session *session, int async)
 {
+    apr_status_t status;
+    
     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c, 
-                  "h2_session(%ld): pre_close", session->id);
-    dispatch_event(session, H2_SESSION_EV_PRE_CLOSE, 0, "timeout");
-    return APR_SUCCESS;
+                  H2_SSSN_MSG(session, "pre_close"));
+    dispatch_event(session, H2_SESSION_EV_PRE_CLOSE, 0, 
+        (session->state == H2_SESSION_ST_IDLE)? "timeout" : NULL);
+    status = session_cleanup(session, "pre_close");
+    if (status == APR_SUCCESS) {
+        /* no one should hold a reference to this session any longer and
+         * the h2_ctx was removed from the connection.
+         * Take the pool (and thus all subpools etc. down now, instead of
+         * during cleanup of main connection pool. */
+        apr_pool_destroy(session->pool);
+    }
+    return status;
 }
diff -up --new-file httpd-2.4.23/modules/http2/h2_session.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_session.h
--- httpd-2.4.23/modules/http2/h2_session.h	2016-05-23 12:55:29.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_session.h	2017-04-10 17:04:55.000000000 +0200
@@ -31,7 +31,7 @@
  * New incoming HEADER frames are converted into a h2_stream+h2_task instance
  * that both represent a HTTP/2 stream, but may have separate lifetimes. This
  * allows h2_task to be scheduled in other threads without semaphores
- * all over the place. It allows task memory to be freed independant of
+ * all over the place. It allows task memory to be freed independent of
  * session lifetime and sessions may close down while tasks are still running.
  *
  *
@@ -49,9 +49,9 @@ struct h2_mplx;
 struct h2_priority;
 struct h2_push;
 struct h2_push_diary;
-struct h2_response;
 struct h2_session;
 struct h2_stream;
+struct h2_stream_monitor;
 struct h2_task;
 struct h2_workers;
 
@@ -65,13 +65,10 @@ typedef enum {
     H2_SESSION_EV_PROTO_ERROR,      /* protocol error */
     H2_SESSION_EV_CONN_TIMEOUT,     /* connection timeout */
     H2_SESSION_EV_NO_IO,            /* nothing has been read or written */
-    H2_SESSION_EV_STREAM_READY,     /* stream signalled availability of headers/data */
     H2_SESSION_EV_DATA_READ,        /* connection data has been read */
     H2_SESSION_EV_NGH2_DONE,        /* nghttp2 wants neither read nor write anything */
     H2_SESSION_EV_MPM_STOPPING,     /* the process is stopping */
     H2_SESSION_EV_PRE_CLOSE,        /* connection will close after this */
-    H2_SESSION_EV_STREAM_OPEN,      /* stream has been opened */
-    H2_SESSION_EV_STREAM_DONE,      /* stream has been handled completely */
 } h2_session_event_t;
 
 typedef struct h2_session {
@@ -87,7 +84,6 @@ typedef struct h2_session {
     struct h2_workers *workers;     /* for executing stream tasks */
     struct h2_filter_cin *cin;      /* connection input filter context */
     h2_conn_io io;                  /* io on httpd conn filters */
-    struct h2_ihash_t *streams;     /* streams handled by this session */
     struct nghttp2_session *ngh2;   /* the nghttp2 session (internal use) */
 
     h2_session_state state;         /* state session is in */
@@ -96,17 +92,17 @@ typedef struct h2_session {
     h2_session_props remote;        /* properites of remote session */
     
     unsigned int reprioritize  : 1; /* scheduled streams priority changed */
-    unsigned int eoc_written   : 1; /* h2 eoc bucket written */
     unsigned int flush         : 1; /* flushing output necessary */
     unsigned int have_read     : 1; /* session has read client data */
     unsigned int have_written  : 1; /* session did write data to client */
-    apr_interval_time_t  wait_us;   /* timout during BUSY_WAIT state, micro secs */
+    apr_interval_time_t  wait_us;   /* timeout during BUSY_WAIT state, micro secs */
     
     struct h2_push_diary *push_diary; /* remember pushes, avoid duplicates */
     
-    int open_streams;               /* number of streams open */
+    struct h2_stream_monitor *monitor;/* monitor callbacks for streams */
+    int open_streams;               /* number of client streams open */
     int unsent_submits;             /* number of submitted, but not yet written responses. */
-    int unsent_promises;            /* number of submitted, but not yet written push promised */
+    int unsent_promises;            /* number of submitted, but not yet written push promises */
                                          
     int responses_submitted;        /* number of http/2 responses submitted */
     int streams_reset;              /* number of http/2 streams reset by client */
@@ -120,7 +116,6 @@ typedef struct h2_session {
     apr_size_t max_stream_count;    /* max number of open streams */
     apr_size_t max_stream_mem;      /* max buffer memory for a single stream */
     
-    apr_time_t start_wait;          /* Time we started waiting for sth. to happen */
     apr_time_t idle_until;          /* Time we shut down due to sheer boredom */
     apr_time_t keep_sync_until;     /* Time we sync wait until passing to async mpm */
     
@@ -130,34 +125,46 @@ typedef struct h2_session {
     char status[64];                /* status message for scoreboard */
     int last_status_code;           /* the one already reported */
     const char *last_status_msg;    /* the one already reported */
+    
+    struct h2_iqueue *in_pending;   /* all streams with input pending */
+    struct h2_iqueue *in_process;   /* all streams ready for processing on slave */
+
 } h2_session;
 
+const char *h2_session_state_str(h2_session_state state);
 
 /**
  * Create a new h2_session for the given connection.
  * The session will apply the configured parameter.
+ * @param psession pointer receiving the created session on success or NULL
  * @param c       the connection to work on
  * @param cfg     the module config to apply
  * @param workers the worker pool to use
  * @return the created session
  */
-h2_session *h2_session_create(conn_rec *c, struct h2_ctx *ctx, 
-                              struct h2_workers *workers);
+apr_status_t h2_session_create(h2_session **psession,
+                               conn_rec *c, struct h2_ctx *ctx, 
+                               struct h2_workers *workers);
 
 /**
  * Create a new h2_session for the given request.
  * The session will apply the configured parameter.
+ * @param psession pointer receiving the created session on success or NULL
  * @param r       the request that was upgraded
  * @param cfg     the module config to apply
  * @param workers the worker pool to use
  * @return the created session
  */
-h2_session *h2_session_rcreate(request_rec *r, struct h2_ctx *ctx,
-                               struct h2_workers *workers);
+apr_status_t h2_session_rcreate(h2_session **psession,
+                                request_rec *r, struct h2_ctx *ctx,
+                                struct h2_workers *workers);
+
+void h2_session_event(h2_session *session, h2_session_event_t ev, 
+                             int err, const char *msg);
 
 /**
  * Process the given HTTP/2 session until it is ended or a fatal
- * error occured.
+ * error occurred.
  *
  * @param session the sessionm to process
  */
@@ -169,14 +176,7 @@ apr_status_t h2_session_process(h2_sessi
 apr_status_t h2_session_pre_close(h2_session *session, int async);
 
 /**
- * Cleanup the session and all objects it still contains. This will not
- * destroy h2_task instances that have not finished yet. 
- * @param session the session to destroy
- */
-void h2_session_eoc_callback(h2_session *session);
-
-/**
- * Called when a serious error occured and the session needs to terminate
+ * Called when a serious error occurred and the session needs to terminate
  * without further connection io.
  * @param session the session to abort
  * @param reason  the apache status that caused the abort
@@ -188,25 +188,6 @@ void h2_session_abort(h2_session *sessio
  */
 void h2_session_close(h2_session *session);
 
-/* Start submitting the response to a stream request. This is possible
- * once we have all the response headers. */
-apr_status_t h2_session_handle_response(h2_session *session,
-                                        struct h2_stream *stream);
-
-/**
- * Create and register a new stream under the given id.
- * 
- * @param session the session to register in
- * @param stream_id the new stream identifier
- * @param initiated_on the stream id this one is initiated on or 0
- * @param req the request for this stream or NULL if not known yet
- * @return the new stream
- */
-struct h2_stream *h2_session_open_stream(h2_session *session, int stream_id,
-                                         int initiated_on, 
-                                         const h2_request *req);
-
-
 /**
  * Returns if client settings have push enabled.
  * @param != 0 iff push is enabled in client settings
@@ -214,12 +195,9 @@ struct h2_stream *h2_session_open_stream
 int h2_session_push_enabled(h2_session *session);
 
 /**
- * Destroy the stream and release it everywhere. Reclaim all resources.
- * @param session the session to which the stream belongs
- * @param stream the stream to destroy
+ * Look up the stream in this session with the given id.
  */
-apr_status_t h2_session_stream_done(h2_session *session, 
-                                    struct h2_stream *stream);
+struct h2_stream *h2_session_stream_get(h2_session *session, int stream_id);
 
 /**
  * Submit a push promise on the stream and schedule the new steam for
@@ -237,5 +215,10 @@ apr_status_t h2_session_set_prio(h2_sess
                                  struct h2_stream *stream, 
                                  const struct h2_priority *prio);
 
+#define H2_SSSN_MSG(s, msg)     \
+    "h2_session(%ld,%s,%d): "msg, s->id, h2_session_state_str(s->state), \
+                            s->open_streams
+
+#define H2_SSSN_LOG(aplogno, s, msg)    aplogno H2_SSSN_MSG(s, msg)
 
 #endif /* defined(__mod_h2__h2_session__) */
diff -up --new-file httpd-2.4.23/modules/http2/h2_stream.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_stream.c
--- httpd-2.4.23/modules/http2/h2_stream.c	2017-12-27 23:01:33.408186020 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_stream.c	2017-09-08 16:36:31.000000000 +0200
@@ -16,6 +16,8 @@
 #include <assert.h>
 #include <stddef.h>
 
+#include <apr_strings.h>
+
 #include <httpd.h>
 #include <http_core.h>
 #include <http_connection.h>
@@ -29,11 +31,10 @@
 #include "h2_conn.h"
 #include "h2_config.h"
 #include "h2_h2.h"
-#include "h2_filter.h"
 #include "h2_mplx.h"
 #include "h2_push.h"
 #include "h2_request.h"
-#include "h2_response.h"
+#include "h2_headers.h"
 #include "h2_session.h"
 #include "h2_stream.h"
 #include "h2_task.h"
@@ -42,207 +43,546 @@
 #include "h2_util.h"
 
 
-static int state_transition[][7] = {
-    /*  ID OP RL RR CI CO CL */
-/*ID*/{  1, 0, 0, 0, 0, 0, 0 },
-/*OP*/{  1, 1, 0, 0, 0, 0, 0 },
-/*RL*/{  0, 0, 1, 0, 0, 0, 0 },
-/*RR*/{  0, 0, 0, 1, 0, 0, 0 },
-/*CI*/{  1, 1, 0, 0, 1, 0, 0 },
-/*CO*/{  1, 1, 0, 0, 0, 1, 0 },
-/*CL*/{  1, 1, 0, 0, 1, 1, 1 },
+static const char *h2_ss_str(h2_stream_state_t state)
+{
+    switch (state) {
+        case H2_SS_IDLE:
+            return "IDLE";
+        case H2_SS_RSVD_L:
+            return "RESERVED_LOCAL";
+        case H2_SS_RSVD_R:
+            return "RESERVED_REMOTE";
+        case H2_SS_OPEN:
+            return "OPEN";
+        case H2_SS_CLOSED_L:
+            return "HALF_CLOSED_LOCAL";
+        case H2_SS_CLOSED_R:
+            return "HALF_CLOSED_REMOTE";
+        case H2_SS_CLOSED:
+            return "CLOSED";
+        case H2_SS_CLEANUP:
+            return "CLEANUP";
+        default:
+            return "UNKNOWN";
+    }
+}
+
+const char *h2_stream_state_str(h2_stream *stream) 
+{
+    return h2_ss_str(stream->state);
+}
+
+/* Abbreviations for stream transit tables */
+#define S_XXX     (-2)                      /* Programming Error */
+#define S_ERR     (-1)                      /* Protocol Error */
+#define S_NOP     (0)                       /* No Change */
+#define S_IDL     (H2_SS_IDL + 1)
+#define S_RS_L    (H2_SS_RSVD_L + 1)
+#define S_RS_R    (H2_SS_RSVD_R + 1)
+#define S_OPEN    (H2_SS_OPEN + 1)
+#define S_CL_L    (H2_SS_CLOSED_L + 1)
+#define S_CL_R    (H2_SS_CLOSED_R + 1)
+#define S_CLS     (H2_SS_CLOSED + 1)
+#define S_CLN     (H2_SS_CLEANUP + 1)
+
+/* state transisitions when certain frame types are sent */
+static int trans_on_send[][H2_SS_MAX] = {
+/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS,  S_CLN, */        
+{ S_ERR, S_ERR,  S_ERR,  S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* DATA */ 
+{ S_ERR, S_ERR,  S_CL_R, S_NOP,  S_NOP,  S_ERR,  S_NOP,  S_NOP, },/* HEADERS */ 
+{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* PRIORITY */    
+{ S_CLS, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* RST_STREAM */ 
+{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* SETTINGS */ 
+{ S_RS_L,S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PUSH_PROMISE */  
+{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PING */ 
+{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* GOAWAY */ 
+{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* WINDOW_UPDATE */ 
+{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* CONT */ 
+};
+/* state transisitions when certain frame types are received */
+static int trans_on_recv[][H2_SS_MAX] = {
+/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS,  S_CLN, */        
+{ S_ERR, S_ERR,  S_ERR,  S_NOP,  S_ERR,  S_NOP,  S_NOP,  S_NOP, },/* DATA */ 
+{ S_OPEN,S_CL_L, S_ERR,  S_NOP,  S_ERR,  S_NOP,  S_NOP,  S_NOP, },/* HEADERS */ 
+{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* PRIORITY */    
+{ S_ERR, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* RST_STREAM */ 
+{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* SETTINGS */ 
+{ S_RS_R,S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PUSH_PROMISE */  
+{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* PING */ 
+{ S_ERR, S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR,  S_ERR, },/* GOAWAY */ 
+{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* WINDOW_UPDATE */ 
+{ S_NOP, S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP,  S_NOP, },/* CONT */ 
+};
+/* state transisitions when certain events happen */
+static int trans_on_event[][H2_SS_MAX] = {
+/*S_IDLE,S_RS_R, S_RS_L, S_OPEN, S_CL_R, S_CL_L, S_CLS,  S_CLN, */        
+{ S_XXX, S_ERR,  S_ERR,  S_CL_L, S_CLS,  S_XXX,  S_XXX,  S_XXX, },/* EV_CLOSED_L*/
+{ S_ERR, S_ERR,  S_ERR,  S_CL_R, S_ERR,  S_CLS,  S_NOP,  S_NOP, },/* EV_CLOSED_R*/
+{ S_CLS, S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_CLS,  S_NOP,  S_NOP, },/* EV_CANCELLED*/
+{ S_NOP, S_XXX,  S_XXX,  S_XXX,  S_XXX,  S_CLS,  S_CLN,  S_XXX, },/* EV_EOS_SENT*/
 };
 
-static void H2_STREAM_OUT_LOG(int lvl, h2_stream *s, char *tag)
+static int on_map(h2_stream_state_t state, int map[H2_SS_MAX])
+{
+    int op = map[state];
+    switch (op) {
+        case S_XXX:
+        case S_ERR:
+            return op;
+        case S_NOP:
+            return state;
+        default:
+            return op-1;
+    }
+}
+
+static int on_frame(h2_stream_state_t state, int frame_type, 
+                    int frame_map[][H2_SS_MAX], apr_size_t maxlen)
+{
+    ap_assert(frame_type >= 0);
+    ap_assert(state >= 0);
+    if (frame_type >= maxlen) {
+        return state; /* NOP, ignore unknown frame types */
+    }
+    return on_map(state, frame_map[frame_type]);
+}
+
+static int on_frame_send(h2_stream_state_t state, int frame_type)
+{
+    return on_frame(state, frame_type, trans_on_send, H2_ALEN(trans_on_send));
+}
+
+static int on_frame_recv(h2_stream_state_t state, int frame_type)
+{
+    return on_frame(state, frame_type, trans_on_recv, H2_ALEN(trans_on_recv));
+}
+
+static int on_event(h2_stream* stream, h2_stream_event_t ev)
+{
+    if (stream->monitor && stream->monitor->on_event) {
+        stream->monitor->on_event(stream->monitor->ctx, stream, ev);
+    }
+    if (ev < H2_ALEN(trans_on_event)) {
+        return on_map(stream->state, trans_on_event[ev]);
+    }
+    return stream->state;
+}
+
+static void H2_STREAM_OUT_LOG(int lvl, h2_stream *s, const char *tag)
 {
     if (APLOG_C_IS_LEVEL(s->session->c, lvl)) {
         conn_rec *c = s->session->c;
         char buffer[4 * 1024];
-        const char *line = "(null)";
         apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]);
         
-        len = h2_util_bb_print(buffer, bmax, tag, "", s->buffer);
-        ap_log_cerror(APLOG_MARK, lvl, 0, c, "bb_dump(%ld-%d): %s", 
-                      c->id, s->id, len? buffer : line);
+        len = h2_util_bb_print(buffer, bmax, tag, "", s->out_buffer);
+        ap_log_cerror(APLOG_MARK, lvl, 0, c, 
+                      H2_STRM_MSG(s, "out-buffer(%s)"), len? buffer : "empty");
+    }
+}
+
+static apr_status_t setup_input(h2_stream *stream) {
+    if (stream->input == NULL) {
+        int empty = (stream->input_eof 
+                     && (!stream->in_buffer 
+                         || APR_BRIGADE_EMPTY(stream->in_buffer)));
+        if (!empty) {
+            h2_beam_create(&stream->input, stream->pool, stream->id, 
+                           "input", H2_BEAM_OWNER_SEND, 0, 
+                           stream->session->s->timeout);
+            h2_beam_send_from(stream->input, stream->pool);
+        }
     }
+    return APR_SUCCESS;
 }
 
-static int set_state(h2_stream *stream, h2_stream_state_t state)
+static apr_status_t close_input(h2_stream *stream)
 {
-    int allowed = state_transition[state][stream->state];
-    if (allowed) {
-        stream->state = state;
-        return 1;
+    conn_rec *c = stream->session->c;
+    apr_status_t status = APR_SUCCESS;
+
+    stream->input_eof = 1;
+    if (stream->input && h2_beam_is_closed(stream->input)) {
+        return APR_SUCCESS;
     }
     
-    ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c, APLOGNO(03081)
-                  "h2_stream(%ld-%d): invalid state transition from %d to %d", 
-                  stream->session->id, stream->id, stream->state, state);
-    return 0;
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+                  H2_STRM_MSG(stream, "closing input"));
+    if (stream->rst_error) {
+        return APR_ECONNRESET;
+    }
+    
+    if (stream->trailers && !apr_is_empty_table(stream->trailers)) {
+        apr_bucket *b;
+        h2_headers *r;
+        
+        if (!stream->in_buffer) {
+            stream->in_buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
+        }
+        
+        r = h2_headers_create(HTTP_OK, stream->trailers, NULL, stream->pool);
+        stream->trailers = NULL;        
+        b = h2_bucket_headers_create(c->bucket_alloc, r);
+        APR_BRIGADE_INSERT_TAIL(stream->in_buffer, b);
+        
+        b = apr_bucket_eos_create(c->bucket_alloc);
+        APR_BRIGADE_INSERT_TAIL(stream->in_buffer, b);
+        
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+                      H2_STRM_MSG(stream, "added trailers"));
+        h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
+    }
+    if (stream->input) {
+        h2_stream_flush_input(stream);
+        return h2_beam_close(stream->input);
+    }
+    return status;
 }
 
-static int close_input(h2_stream *stream) 
+static apr_status_t close_output(h2_stream *stream)
 {
-    switch (stream->state) {
-        case H2_STREAM_ST_CLOSED_INPUT:
-        case H2_STREAM_ST_CLOSED:
-            return 0; /* ignore, idempotent */
-        case H2_STREAM_ST_CLOSED_OUTPUT:
-            /* both closed now */
-            set_state(stream, H2_STREAM_ST_CLOSED);
-            break;
-        default:
-            /* everything else we jump to here */
-            set_state(stream, H2_STREAM_ST_CLOSED_INPUT);
-            break;
+    if (!stream->output || h2_beam_is_closed(stream->output)) {
+        return APR_SUCCESS;
     }
-    return 1;
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+                  H2_STRM_MSG(stream, "closing output"));
+    return h2_beam_leave(stream->output);
 }
 
-static int input_closed(h2_stream *stream) 
+static void on_state_enter(h2_stream *stream) 
 {
-    switch (stream->state) {
-        case H2_STREAM_ST_OPEN:
-        case H2_STREAM_ST_CLOSED_OUTPUT:
-            return 0;
-        default:
-            return 1;
+    if (stream->monitor && stream->monitor->on_state_enter) {
+        stream->monitor->on_state_enter(stream->monitor->ctx, stream);
+    }
+}
+
+static void on_state_event(h2_stream *stream, h2_stream_event_t ev) 
+{
+    if (stream->monitor && stream->monitor->on_state_event) {
+        stream->monitor->on_state_event(stream->monitor->ctx, stream, ev);
     }
 }
 
-static int close_output(h2_stream *stream) 
+static void on_state_invalid(h2_stream *stream) 
 {
+    if (stream->monitor && stream->monitor->on_state_invalid) {
+        stream->monitor->on_state_invalid(stream->monitor->ctx, stream);
+    }
+    /* stream got an event/frame invalid in its state */
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+                  H2_STRM_MSG(stream, "invalid state event")); 
     switch (stream->state) {
-        case H2_STREAM_ST_CLOSED_OUTPUT:
-        case H2_STREAM_ST_CLOSED:
-            return 0; /* ignore, idempotent */
-        case H2_STREAM_ST_CLOSED_INPUT:
-            /* both closed now */
-            set_state(stream, H2_STREAM_ST_CLOSED);
+        case H2_SS_OPEN:
+        case H2_SS_RSVD_L:
+        case H2_SS_RSVD_R:
+        case H2_SS_CLOSED_L:
+        case H2_SS_CLOSED_R:
+            h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
             break;
         default:
-            /* everything else we jump to here */
-            set_state(stream, H2_STREAM_ST_CLOSED_OUTPUT);
             break;
     }
-    return 1;
 }
 
-static int input_open(const h2_stream *stream) 
+static apr_status_t transit(h2_stream *stream, int new_state)
 {
-    switch (stream->state) {
-        case H2_STREAM_ST_OPEN:
-        case H2_STREAM_ST_CLOSED_OUTPUT:
-            return 1;
+    if (new_state == stream->state) {
+        return APR_SUCCESS;
+    }
+    else if (new_state < 0) {
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c, 
+                      H2_STRM_LOG(APLOGNO(03081), stream, "invalid transition"));
+        on_state_invalid(stream);
+        return APR_EINVAL;
+    }
+    
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c, 
+                  H2_STRM_MSG(stream, "transit to [%s]"), h2_ss_str(new_state));
+    stream->state = new_state;
+    switch (new_state) {
+        case H2_SS_IDLE:
+            break;
+        case H2_SS_RSVD_L:
+            close_input(stream);
+            break;
+        case H2_SS_RSVD_R:
+            break;
+        case H2_SS_OPEN:
+            break;
+        case H2_SS_CLOSED_L:
+            close_output(stream);
+            break;
+        case H2_SS_CLOSED_R:
+            close_input(stream);
+            break;
+        case H2_SS_CLOSED:
+            close_input(stream);
+            close_output(stream);
+            if (stream->out_buffer) {
+                apr_brigade_cleanup(stream->out_buffer);
+            }
+            break;
+        case H2_SS_CLEANUP:
+            break;
+    }
+    on_state_enter(stream);
+    return APR_SUCCESS;
+}
+
+void h2_stream_set_monitor(h2_stream *stream, h2_stream_monitor *monitor)
+{
+    stream->monitor = monitor;
+}
+
+void h2_stream_dispatch(h2_stream *stream, h2_stream_event_t ev)
+{
+    int new_state;
+    
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+                  H2_STRM_MSG(stream, "dispatch event %d"), ev);
+    new_state = on_event(stream, ev);
+    if (new_state < 0) {
+        ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, stream->session->c, 
+                      H2_STRM_LOG(APLOGNO(10002), stream, "invalid event %d"), ev);
+        on_state_invalid(stream);
+        AP_DEBUG_ASSERT(new_state > S_XXX);
+        return;
+    }
+    else if (new_state == stream->state) {
+        /* nop */
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
+                      H2_STRM_MSG(stream, "non-state event %d"), ev);
+        return;
+    }
+    else {
+        on_state_event(stream, ev);
+        transit(stream, new_state);
+    }
+}
+
+static void set_policy_for(h2_stream *stream, h2_request *r) 
+{
+    int enabled = h2_session_push_enabled(stream->session);
+    stream->push_policy = h2_push_policy_determine(r->headers, stream->pool, 
+                                                   enabled);
+    r->serialize = h2_config_geti(stream->session->config, H2_CONF_SER_HEADERS);
+}
+
+apr_status_t h2_stream_send_frame(h2_stream *stream, int ftype, int flags)
+{
+    apr_status_t status = APR_SUCCESS;
+    int new_state, eos = 0;
+
+    new_state = on_frame_send(stream->state, ftype);
+    if (new_state < 0) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c, 
+                      H2_STRM_MSG(stream, "invalid frame %d send"), ftype);
+        AP_DEBUG_ASSERT(new_state > S_XXX);
+        return transit(stream, new_state);
+    }
+    
+    switch (ftype) {
+        case NGHTTP2_DATA:
+            eos = (flags & NGHTTP2_FLAG_END_STREAM);
+            break;
+            
+        case NGHTTP2_HEADERS:
+            eos = (flags & NGHTTP2_FLAG_END_STREAM);
+            break;
+            
+        case NGHTTP2_PUSH_PROMISE:
+                /* start pushed stream */
+                ap_assert(stream->request == NULL);
+                ap_assert(stream->rtmp != NULL);
+                status = h2_request_end_headers(stream->rtmp, stream->pool, 1);
+                if (status != APR_SUCCESS) {
+                    return status;
+                }
+                set_policy_for(stream, stream->rtmp);
+                stream->request = stream->rtmp;
+                stream->rtmp = NULL;
+            break;
+            
         default:
-            return 0;
+            break;
+    }
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c, 
+                  H2_STRM_MSG(stream, "send frame %d, eos=%d"), ftype, eos);
+    status = transit(stream, new_state);
+    if (status == APR_SUCCESS && eos) {
+        status = transit(stream, on_event(stream, H2_SEV_CLOSED_L));
     }
+    return status;
 }
 
-static int output_open(h2_stream *stream) 
+apr_status_t h2_stream_recv_frame(h2_stream *stream, int ftype, int flags)
 {
-    switch (stream->state) {
-        case H2_STREAM_ST_OPEN:
-        case H2_STREAM_ST_CLOSED_INPUT:
-            return 1;
+    apr_status_t status = APR_SUCCESS;
+    int new_state, eos = 0;
+
+    new_state = on_frame_recv(stream->state, ftype);
+    if (new_state < 0) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c, 
+                      H2_STRM_MSG(stream, "invalid frame %d recv"), ftype);
+        AP_DEBUG_ASSERT(new_state > S_XXX);
+        return transit(stream, new_state);
+    }
+    
+    switch (ftype) {
+        case NGHTTP2_DATA:
+            eos = (flags & NGHTTP2_FLAG_END_STREAM);
+            break;
+            
+        case NGHTTP2_HEADERS:
+            eos = (flags & NGHTTP2_FLAG_END_STREAM);
+            if (stream->state == H2_SS_OPEN) {
+                /* trailer HEADER */
+                if (!eos) {
+                    h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR);
+                }
+            }
+            else {
+                /* request HEADER */
+                ap_assert(stream->request == NULL);
+                if (stream->rtmp == NULL) {
+                    /* This can only happen, if the stream has received no header
+                     * name/value pairs at all. The lastest nghttp2 version have become
+                     * pretty good at detecting this early. In any case, we have
+                     * to abort the connection here, since this is clearly a protocol error */
+                    return APR_EINVAL;
+                }
+                status = h2_request_end_headers(stream->rtmp, stream->pool, eos);
+                if (status != APR_SUCCESS) {
+                    return status;
+                }
+                set_policy_for(stream, stream->rtmp);
+                stream->request = stream->rtmp;
+                stream->rtmp = NULL;
+            }
+            break;
+            
         default:
-            return 0;
+            break;
+    }
+    status = transit(stream, new_state);
+    if (status == APR_SUCCESS && eos) {
+        status = transit(stream, on_event(stream, H2_SEV_CLOSED_R));
     }
+    return status;
 }
 
-static apr_status_t stream_pool_cleanup(void *ctx)
+apr_status_t h2_stream_flush_input(h2_stream *stream)
 {
-    h2_stream *stream = ctx;
-    apr_status_t status;
+    apr_status_t status = APR_SUCCESS;
     
-    if (stream->input) {
-        h2_beam_destroy(stream->input);
-        stream->input = NULL;
+    if (stream->in_buffer && !APR_BRIGADE_EMPTY(stream->in_buffer)) {
+        setup_input(stream);
+        status = h2_beam_send(stream->input, stream->in_buffer, APR_BLOCK_READ);
+        stream->in_last_write = apr_time_now();
     }
-    if (stream->files) {
-        apr_file_t *file;
-        int i;
-        for (i = 0; i < stream->files->nelts; ++i) {
-            file = APR_ARRAY_IDX(stream->files, i, apr_file_t*);
-            status = apr_file_close(file);
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, stream->session->c, 
-                          "h2_stream(%ld-%d): destroy, closed file %d", 
-                          stream->session->id, stream->id, i);
-        }
-        stream->files = NULL;
+    if (stream->input_eof 
+        && stream->input && !h2_beam_is_closed(stream->input)) {
+        status = h2_beam_close(stream->input);
     }
-    return APR_SUCCESS;
+    return status;
 }
 
-h2_stream *h2_stream_open(int id, apr_pool_t *pool, h2_session *session,
-                          int initiated_on, const h2_request *creq)
+apr_status_t h2_stream_recv_DATA(h2_stream *stream, uint8_t flags,
+                                    const uint8_t *data, size_t len)
 {
-    h2_request *req;
-    h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
+    h2_session *session = stream->session;
+    apr_status_t status = APR_SUCCESS;
     
-    stream->id        = id;
-    stream->state     = H2_STREAM_ST_IDLE;
-    stream->pool      = pool;
-    stream->session   = session;
-    set_state(stream, H2_STREAM_ST_OPEN);
-    
-    if (creq) {
-        /* take it into out pool and assure correct id's */
-        req = h2_request_clone(pool, creq);
-        req->id = id;
-        req->initiated_on = initiated_on;
+    stream->in_data_frames++;
+    if (len > 0) {
+        if (APLOGctrace3(session->c)) {
+            const char *load = apr_pstrndup(stream->pool, (const char *)data, len);
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, session->c,
+                          H2_STRM_MSG(stream, "recv DATA, len=%d: -->%s<--"), 
+                          (int)len, load);
+        }
+        else {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, session->c,
+                          H2_STRM_MSG(stream, "recv DATA, len=%d"), (int)len);
+        }
+        stream->in_data_octets += len;
+        if (!stream->in_buffer) {
+            stream->in_buffer = apr_brigade_create(stream->pool, 
+                                                   session->c->bucket_alloc);
+        }
+        apr_brigade_write(stream->in_buffer, NULL, NULL, (const char *)data, len);
+        h2_stream_dispatch(stream, H2_SEV_IN_DATA_PENDING);
     }
-    else {
-        req = h2_req_create(id, pool, 
-                h2_config_geti(session->config, H2_CONF_SER_HEADERS));
+    return status;
+}
+
+static void prep_output(h2_stream *stream) {
+    conn_rec *c = stream->session->c;
+    if (!stream->out_buffer) {
+        stream->out_buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
     }
-    stream->request = req; 
+}
+
+h2_stream *h2_stream_create(int id, apr_pool_t *pool, h2_session *session,
+                            h2_stream_monitor *monitor, int initiated_on)
+{
+    h2_stream *stream = apr_pcalloc(pool, sizeof(h2_stream));
     
-    apr_pool_cleanup_register(pool, stream, stream_pool_cleanup, 
-                              apr_pool_cleanup_null);
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, APLOGNO(03082)
-                  "h2_stream(%ld-%d): opened", session->id, stream->id);
+    stream->id           = id;
+    stream->initiated_on = initiated_on;
+    stream->created      = apr_time_now();
+    stream->state        = H2_SS_IDLE;
+    stream->pool         = pool;
+    stream->session      = session;
+    stream->monitor      = monitor;
+    stream->max_mem      = session->max_stream_mem;
+    
+#ifdef H2_NG2_LOCAL_WIN_SIZE
+    stream->in_window_size = 
+        nghttp2_session_get_stream_local_window_size(
+            stream->session->ngh2, stream->id);
+#endif
+
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, session->c, 
+                  H2_STRM_LOG(APLOGNO(03082), stream, "created"));
+    on_state_enter(stream);
     return stream;
 }
 
 void h2_stream_cleanup(h2_stream *stream)
 {
-    AP_DEBUG_ASSERT(stream);
-    if (stream->buffer) {
-        apr_brigade_cleanup(stream->buffer);
+    apr_status_t status;
+    
+    ap_assert(stream);
+    if (stream->out_buffer) {
+        /* remove any left over output buckets that may still have
+         * references into request pools */
+        apr_brigade_cleanup(stream->out_buffer);
     }
     if (stream->input) {
-        apr_status_t status;
-        status = h2_beam_shutdown(stream->input, APR_NONBLOCK_READ, 1);
+        h2_beam_abort(stream->input);
+        status = h2_beam_wait_empty(stream->input, APR_NONBLOCK_READ);
         if (status == APR_EAGAIN) {
             ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c, 
-                          "h2_stream(%ld-%d): wait on input shutdown", 
-                          stream->session->id, stream->id);
-            status = h2_beam_shutdown(stream->input, APR_BLOCK_READ, 1);
+                          H2_STRM_MSG(stream, "wait on input drain"));
+            status = h2_beam_wait_empty(stream->input, APR_BLOCK_READ);
             ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, stream->session->c, 
-                          "h2_stream(%ld-%d): input shutdown returned", 
-                          stream->session->id, stream->id);
+                          H2_STRM_MSG(stream, "input drain returned"));
         }
     }
 }
 
 void h2_stream_destroy(h2_stream *stream)
 {
-    AP_DEBUG_ASSERT(stream);
+    ap_assert(stream);
     ap_log_cerror(APLOG_MARK, APLOG_TRACE3, 0, stream->session->c, 
-                  "h2_stream(%ld-%d): destroy", 
-                  stream->session->id, stream->id);
+                  H2_STRM_MSG(stream, "destroy"));
     if (stream->pool) {
         apr_pool_destroy(stream->pool);
+        stream->pool = NULL;
     }
 }
 
-void h2_stream_eos_destroy(h2_stream *stream)
-{
-    h2_session_stream_done(stream->session, stream);
-    /* stream possibly destroyed */
-}
-
 apr_pool_t *h2_stream_detach_pool(h2_stream *stream)
 {
     apr_pool_t *pool = stream->pool;
@@ -250,374 +590,352 @@ apr_pool_t *h2_stream_detach_pool(h2_str
     return pool;
 }
 
-void h2_stream_rst(h2_stream *stream, int error_code)
+apr_status_t h2_stream_prep_processing(h2_stream *stream)
 {
-    stream->rst_error = error_code;
-    close_input(stream);
-    close_output(stream);
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
-                  "h2_stream(%ld-%d): reset, error=%d", 
-                  stream->session->id, stream->id, error_code);
+    if (stream->request) {
+        const h2_request *r = stream->request;
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+                      H2_STRM_MSG(stream, "schedule %s %s://%s%s chunked=%d"),
+                      r->method, r->scheme, r->authority, r->path, r->chunked);
+        setup_input(stream);
+        stream->scheduled = 1;
+        return APR_SUCCESS;
+    }
+    return APR_EINVAL;
 }
 
-struct h2_response *h2_stream_get_response(h2_stream *stream)
+void h2_stream_rst(h2_stream *stream, int error_code)
 {
-    return stream->response;
+    stream->rst_error = error_code;
+    if (stream->input) {
+        h2_beam_abort(stream->input);
+    }
+    if (stream->output) {
+        h2_beam_leave(stream->output);
+    }
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+                  H2_STRM_MSG(stream, "reset, error=%d"), error_code);
+    h2_stream_dispatch(stream, H2_SEV_CANCELLED);
 }
 
-apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r)
+apr_status_t h2_stream_set_request_rec(h2_stream *stream, 
+                                       request_rec *r, int eos)
 {
+    h2_request *req;
     apr_status_t status;
-    AP_DEBUG_ASSERT(stream);
+
+    ap_assert(stream->request == NULL);
+    ap_assert(stream->rtmp == NULL);
     if (stream->rst_error) {
         return APR_ECONNRESET;
     }
-    set_state(stream, H2_STREAM_ST_OPEN);
-    status = h2_request_rwrite(stream->request, stream->pool, r);
-    stream->request->serialize = h2_config_geti(h2_config_rget(r), 
-                                                H2_CONF_SER_HEADERS);
-    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(03058)
-                  "h2_request(%d): rwrite %s host=%s://%s%s",
-                  stream->request->id, stream->request->method, 
-                  stream->request->scheme, stream->request->authority, 
-                  stream->request->path);
-
+    status = h2_request_rcreate(&req, stream->pool, r);
+    if (status == APR_SUCCESS) {
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, 
+                      H2_STRM_LOG(APLOGNO(03058), stream, 
+                      "set_request_rec %s host=%s://%s%s"),
+                      req->method, req->scheme, req->authority, req->path);
+        stream->rtmp = req;
+        /* simulate the frames that led to this */
+        return h2_stream_recv_frame(stream, NGHTTP2_HEADERS, 
+                                    NGHTTP2_FLAG_END_STREAM);
+    }
     return status;
 }
 
+void h2_stream_set_request(h2_stream *stream, const h2_request *r)
+{
+    ap_assert(stream->request == NULL);
+    ap_assert(stream->rtmp == NULL);
+    stream->rtmp = h2_request_clone(stream->pool, r);
+}
+
+static void set_error_response(h2_stream *stream, int http_status)
+{
+    if (!h2_stream_is_ready(stream)) {
+        conn_rec *c = stream->session->c;
+        apr_bucket *b;
+        h2_headers *response;
+        
+        response = h2_headers_die(http_status, stream->request, stream->pool);
+        prep_output(stream);
+        b = apr_bucket_eos_create(c->bucket_alloc);
+        APR_BRIGADE_INSERT_HEAD(stream->out_buffer, b);
+        b = h2_bucket_headers_create(c->bucket_alloc, response);
+        APR_BRIGADE_INSERT_HEAD(stream->out_buffer, b);
+    }
+}
+
+static apr_status_t add_trailer(h2_stream *stream,
+                                const char *name, size_t nlen,
+                                const char *value, size_t vlen)
+{
+    conn_rec *c = stream->session->c;
+    char *hname, *hvalue;
+
+    if (nlen == 0 || name[0] == ':') {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, APR_EINVAL, c, 
+                      H2_STRM_LOG(APLOGNO(03060), stream, 
+                      "pseudo header in trailer"));
+        return APR_EINVAL;
+    }
+    if (h2_req_ignore_trailer(name, nlen)) {
+        return APR_SUCCESS;
+    }
+    if (!stream->trailers) {
+        stream->trailers = apr_table_make(stream->pool, 5);
+    }
+    hname = apr_pstrndup(stream->pool, name, nlen);
+    hvalue = apr_pstrndup(stream->pool, value, vlen);
+    h2_util_camel_case_header(hname, nlen);
+    apr_table_mergen(stream->trailers, hname, hvalue);
+    
+    return APR_SUCCESS;
+}
+
 apr_status_t h2_stream_add_header(h2_stream *stream,
                                   const char *name, size_t nlen,
                                   const char *value, size_t vlen)
 {
+    h2_session *session = stream->session;
     int error = 0;
-    AP_DEBUG_ASSERT(stream);
-    if (stream->response) {
+    apr_status_t status;
+    
+    if (stream->has_response) {
         return APR_EINVAL;    
     }
     ++stream->request_headers_added;
     if (name[0] == ':') {
-        if ((vlen) > stream->session->s->limit_req_line) {
+        if ((vlen) > session->s->limit_req_line) {
             /* pseudo header: approximation of request line size check */
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
-                          "h2_stream(%ld-%d): pseudo header %s too long", 
-                          stream->session->id, stream->id, name);
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+                          H2_STRM_MSG(stream, "pseudo %s too long"), name);
             error = HTTP_REQUEST_URI_TOO_LARGE;
         }
     }
-    else if ((nlen + 2 + vlen) > stream->session->s->limit_req_fieldsize) {
+    else if ((nlen + 2 + vlen) > session->s->limit_req_fieldsize) {
         /* header too long */
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
-                      "h2_stream(%ld-%d): header %s too long", 
-                      stream->session->id, stream->id, name);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+                      H2_STRM_MSG(stream, "header %s too long"), name);
         error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
     }
     
-    if (stream->request_headers_added 
-        > stream->session->s->limit_req_fields + 4) {
+    if (stream->request_headers_added > session->s->limit_req_fields + 4) {
         /* too many header lines, include 4 pseudo headers */
         if (stream->request_headers_added 
-            > stream->session->s->limit_req_fields + 4 + 100) {
+            > session->s->limit_req_fields + 4 + 100) {
             /* yeah, right */
+            h2_stream_rst(stream, H2_ERR_ENHANCE_YOUR_CALM);
             return APR_ECONNRESET;
         }
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
-                      "h2_stream(%ld-%d): too many header lines", 
-                      stream->session->id, stream->id);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+                      H2_STRM_MSG(stream, "too many header lines")); 
         error = HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE;
     }
     
-    if (h2_stream_is_scheduled(stream)) {
-        return h2_request_add_trailer(stream->request, stream->pool,
-                                      name, nlen, value, vlen);
-    }
-    else if (error) {
-        return h2_stream_set_error(stream, error); 
-    }
-    else {
-        if (!input_open(stream)) {
-            return APR_ECONNRESET;
+    if (error) {
+        set_error_response(stream, error);
+        return APR_EINVAL; 
+    }
+    else if (H2_SS_IDLE == stream->state) {
+        if (!stream->rtmp) {
+            stream->rtmp = h2_req_create(stream->id, stream->pool, 
+                                         NULL, NULL, NULL, NULL, NULL, 0);
         }
-        return h2_request_add_header(stream->request, stream->pool,
-                                     name, nlen, value, vlen);
-    }
-}
-
-apr_status_t h2_stream_schedule(h2_stream *stream, int eos, int push_enabled, 
-                                h2_stream_pri_cmp *cmp, void *ctx)
-{
-    apr_status_t status;
-    AP_DEBUG_ASSERT(stream);
-    AP_DEBUG_ASSERT(stream->session);
-    AP_DEBUG_ASSERT(stream->session->mplx);
-    
-    if (!output_open(stream)) {
-        return APR_ECONNRESET;
-    }
-    if (stream->scheduled) {
-        return APR_EINVAL;
-    }
-    if (eos) {
-        close_input(stream);
+        status = h2_request_add_header(stream->rtmp, stream->pool,
+                                       name, nlen, value, vlen);
     }
-    
-    if (stream->response) {
-        /* already have a resonse, probably a HTTP error code */
-        return h2_mplx_process(stream->session->mplx, stream, cmp, ctx);
-    }
-    
-    /* Seeing the end-of-headers, we have everything we need to 
-     * start processing it.
-     */
-    status = h2_request_end_headers(stream->request, stream->pool, 
-                                    eos, push_enabled);
-    if (status == APR_SUCCESS) {
-        stream->request->body = !eos;
-        stream->scheduled = 1;
-        stream->input_remaining = stream->request->content_length;
-        
-        status = h2_mplx_process(stream->session->mplx, stream, cmp, ctx);
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
-                      "h2_stream(%ld-%d): scheduled %s %s://%s%s",
-                      stream->session->id, stream->id,
-                      stream->request->method, stream->request->scheme,
-                      stream->request->authority, stream->request->path);
+    else if (H2_SS_OPEN == stream->state) {
+        status = add_trailer(stream, name, nlen, value, vlen);
     }
     else {
-        h2_stream_rst(stream, H2_ERR_INTERNAL_ERROR);
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, stream->session->c,
-                      "h2_stream(%ld-%d): RST=2 (internal err) %s %s://%s%s",
-                      stream->session->id, stream->id,
-                      stream->request->method, stream->request->scheme,
-                      stream->request->authority, stream->request->path);
-    }
-    
-    return status;
-}
-
-int h2_stream_is_scheduled(const h2_stream *stream)
-{
-    return stream->scheduled;
-}
-
-apr_status_t h2_stream_close_input(h2_stream *stream)
-{
-    apr_status_t status = APR_SUCCESS;
-    
-    AP_DEBUG_ASSERT(stream);
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
-                  "h2_stream(%ld-%d): closing input",
-                  stream->session->id, stream->id);
-                  
-    if (stream->rst_error) {
-        return APR_ECONNRESET;
+        status = APR_EINVAL;
     }
     
-    if (close_input(stream) && stream->input) {
-        status = h2_beam_close(stream->input);
+    if (status != APR_SUCCESS) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, session->c,
+                      H2_STRM_MSG(stream, "header %s not accepted"), name);
+        h2_stream_dispatch(stream, H2_SEV_CANCELLED);
     }
     return status;
 }
 
-apr_status_t h2_stream_write_data(h2_stream *stream,
-                                  const char *data, size_t len, int eos)
+static apr_bucket *get_first_headers_bucket(apr_bucket_brigade *bb)
 {
-    conn_rec *c = stream->session->c;
-    apr_status_t status = APR_SUCCESS;
-    
-    AP_DEBUG_ASSERT(stream);
-    if (!stream->input) {
-        return APR_EOF;
-    }
-    if (input_closed(stream) || !stream->request->eoh) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                      "h2_stream(%ld-%d): writing denied, closed=%d, eoh=%d", 
-                      stream->session->id, stream->id, input_closed(stream),
-                      stream->request->eoh);
-        return APR_EINVAL;
-    }
-
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                  "h2_stream(%ld-%d): add %ld input bytes", 
-                  stream->session->id, stream->id, (long)len);
-
-    if (!stream->request->chunked) {
-        stream->input_remaining -= len;
-        if (stream->input_remaining < 0) {
-            ap_log_cerror(APLOG_MARK, APLOG_WARNING, 0, c,
-                          APLOGNO(02961) 
-                          "h2_stream(%ld-%d): got %ld more content bytes than announced "
-                          "in content-length header: %ld", 
-                          stream->session->id, stream->id,
-                          (long)stream->request->content_length, 
-                          -(long)stream->input_remaining);
-            h2_stream_rst(stream, H2_ERR_PROTOCOL_ERROR);
-            return APR_ECONNABORTED;
+    if (bb) {
+        apr_bucket *b = APR_BRIGADE_FIRST(bb);
+        while (b != APR_BRIGADE_SENTINEL(bb)) {
+            if (H2_BUCKET_IS_HEADERS(b)) {
+                return b;
+            }
+            b = APR_BUCKET_NEXT(b);
         }
     }
-    
-    if (!stream->tmp) {
-        stream->tmp = apr_brigade_create(stream->pool, c->bucket_alloc);
-    }
-    apr_brigade_write(stream->tmp, NULL, NULL, data, len);
-    if (eos) {
-        APR_BRIGADE_INSERT_TAIL(stream->tmp, 
-                                apr_bucket_eos_create(c->bucket_alloc)); 
-        close_input(stream);
-    }
-    
-    status = h2_beam_send(stream->input, stream->tmp, APR_BLOCK_READ);
-    apr_brigade_cleanup(stream->tmp);
-    return status;
-}
-
-void h2_stream_set_suspended(h2_stream *stream, int suspended)
-{
-    AP_DEBUG_ASSERT(stream);
-    stream->suspended = !!suspended;
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, stream->session->c,
-                  "h2_stream(%ld-%d): suspended=%d",
-                  stream->session->id, stream->id, stream->suspended);
-}
-
-int h2_stream_is_suspended(const h2_stream *stream)
-{
-    AP_DEBUG_ASSERT(stream);
-    return stream->suspended;
+    return NULL;
 }
 
-static apr_status_t fill_buffer(h2_stream *stream, apr_size_t amount)
-{
-    conn_rec *c = stream->session->c;
-    apr_bucket *b;
-    apr_status_t status;
-    
-    if (!stream->output) {
-        return APR_EOF;
-    }
-    status = h2_beam_receive(stream->output, stream->buffer, 
-                             APR_NONBLOCK_READ, amount);
-    /* The buckets we reveive are using the stream->buffer pool as
-     * lifetime which is exactly what we want since this is stream->pool.
-     *
-     * However: when we send these buckets down the core output filters, the
-     * filter might decide to setaside them into a pool of its own. And it
-     * might decide, after having sent the buckets, to clear its pool.
-     *
-     * This is problematic for file buckets because it then closed the contained
-     * file. Any split off buckets we sent afterwards will result in a 
-     * APR_EBADF.
-     */
-    for (b = APR_BRIGADE_FIRST(stream->buffer);
-         b != APR_BRIGADE_SENTINEL(stream->buffer);
-         b = APR_BUCKET_NEXT(b)) {
-        if (APR_BUCKET_IS_FILE(b)) {
-            apr_bucket_file *f = (apr_bucket_file *)b->data;
-            apr_pool_t *fpool = apr_file_pool_get(f->fd);
-            if (fpool != c->pool) {
-                apr_bucket_setaside(b, c->pool);
-                if (!stream->files) {
-                    stream->files = apr_array_make(stream->pool, 
-                                                   5, sizeof(apr_file_t*));
+static apr_status_t add_buffered_data(h2_stream *stream, apr_off_t requested,
+                                      apr_off_t *plen, int *peos, int *is_all, 
+                                      h2_headers **pheaders)
+{
+    apr_bucket *b, *e;
+    
+    *peos = 0;
+    *plen = 0;
+    *is_all = 0;
+    if (pheaders) {
+        *pheaders = NULL;
+    }
+
+    H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "add_buffered_data");
+    b = APR_BRIGADE_FIRST(stream->out_buffer);
+    while (b != APR_BRIGADE_SENTINEL(stream->out_buffer)) {
+        e = APR_BUCKET_NEXT(b);
+        if (APR_BUCKET_IS_METADATA(b)) {
+            if (APR_BUCKET_IS_FLUSH(b)) {
+                APR_BUCKET_REMOVE(b);
+                apr_bucket_destroy(b);
+            }
+            else if (APR_BUCKET_IS_EOS(b)) {
+                *peos = 1;
+                return APR_SUCCESS;
+            }
+            else if (H2_BUCKET_IS_HEADERS(b)) {
+                if (*plen > 0) {
+                    /* data before the response, can only return up to here */
+                    return APR_SUCCESS;
+                }
+                else if (pheaders) {
+                    *pheaders = h2_bucket_headers_get(b);
+                    APR_BUCKET_REMOVE(b);
+                    apr_bucket_destroy(b);
+                    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
+                                  H2_STRM_MSG(stream, "prep, -> response %d"), 
+                                  (*pheaders)->status);
+                    return APR_SUCCESS;
+                }
+                else {
+                    return APR_EAGAIN;
                 }
-                APR_ARRAY_PUSH(stream->files, apr_file_t*) = f->fd;
             }
         }
+        else if (b->length == 0) {
+            APR_BUCKET_REMOVE(b);
+            apr_bucket_destroy(b);
+        }
+        else {
+            ap_assert(b->length != (apr_size_t)-1);
+            *plen += b->length;
+            if (*plen >= requested) {
+                *plen = requested;
+                return APR_SUCCESS;
+            }
+        }
+        b = e;
     }
-    return status;
+    *is_all = 1;
+    return APR_SUCCESS;
 }
 
-apr_status_t h2_stream_set_response(h2_stream *stream, h2_response *response,
-                                    h2_bucket_beam *output)
+apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen, 
+                                   int *peos, h2_headers **pheaders)
 {
     apr_status_t status = APR_SUCCESS;
-    conn_rec *c = stream->session->c;
-    
-    if (!output_open(stream)) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                      "h2_stream(%ld-%d): output closed", 
-                      stream->session->id, stream->id);
-        return APR_ECONNRESET;
-    }
-    
-    stream->response = response;
-    stream->output = output;
-    stream->buffer = apr_brigade_create(stream->pool, c->bucket_alloc);
-    
-    h2_stream_filter(stream);
-    if (stream->output) {
-        status = fill_buffer(stream, 0);
-    }
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
-                  "h2_stream(%ld-%d): set_response(%d)", 
-                  stream->session->id, stream->id, 
-                  stream->response->http_status);
-    return status;
-}
+    apr_off_t requested, missing, max_chunk = H2_DATA_CHUNK_SIZE;
+    conn_rec *c;
+    int complete;
 
-apr_status_t h2_stream_set_error(h2_stream *stream, int http_status)
-{
-    h2_response *response;
+    ap_assert(stream);
     
-    if (stream->submitted) {
-        return APR_EINVAL;
-    }
-    response = h2_response_die(stream->id, http_status, stream->request, 
-                               stream->pool);
-    return h2_stream_set_response(stream, response, NULL);
-}
-
-static const apr_size_t DATA_CHUNK_SIZE = ((16*1024) - 100 - 9); 
-
-apr_status_t h2_stream_out_prepare(h2_stream *stream,
-                                   apr_off_t *plen, int *peos)
-{
-    conn_rec *c = stream->session->c;
-    apr_status_t status = APR_SUCCESS;
-    apr_off_t requested;
-
     if (stream->rst_error) {
         *plen = 0;
         *peos = 1;
         return APR_ECONNRESET;
     }
+    
+    c = stream->session->c;
+    prep_output(stream);
 
-    if (*plen > 0) {
-        requested = H2MIN(*plen, DATA_CHUNK_SIZE);
+    /* determine how much we'd like to send. We cannot send more than
+     * is requested. But we can reduce the size in case the master
+     * connection operates in smaller chunks. (TSL warmup) */
+    if (stream->session->io.write_size > 0) {
+        max_chunk = stream->session->io.write_size - 9; /* header bits */ 
+    }
+    requested = (*plen > 0)? H2MIN(*plen, max_chunk) : max_chunk;
+    
+    /* count the buffered data until eos or a headers bucket */
+    status = add_buffered_data(stream, requested, plen, peos, &complete, pheaders);
+    
+    if (status == APR_EAGAIN) {
+        /* TODO: ugly, someone needs to retrieve the response first */
+        h2_mplx_keep_active(stream->session->mplx, stream);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                      H2_STRM_MSG(stream, "prep, response eagain"));
+        return status;
     }
-    else {
-        requested = DATA_CHUNK_SIZE;
+    else if (status != APR_SUCCESS) {
+        return status;
+    }
+    
+    if (pheaders && *pheaders) {
+        return APR_SUCCESS;
     }
-    *plen = requested;
     
-    H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "h2_stream_out_prepare_pre");
-    h2_util_bb_avail(stream->buffer, plen, peos);
-    if (!*peos && *plen < requested) {
-        /* try to get more data */
-        status = fill_buffer(stream, (requested - *plen) + DATA_CHUNK_SIZE);
-        if (APR_STATUS_IS_EOF(status)) {
+    /* If there we do not have enough buffered data to satisfy the requested
+     * length *and* we counted the _complete_ buffer (and did not stop in the middle
+     * because of meta data there), lets see if we can read more from the
+     * output beam */
+    missing = H2MIN(requested, stream->max_mem) - *plen;
+    if (complete && !*peos && missing > 0) {
+        apr_status_t rv = APR_EOF;
+        
+        if (stream->output) {
+            H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "pre");
+            rv = h2_beam_receive(stream->output, stream->out_buffer, 
+                                 APR_NONBLOCK_READ, stream->max_mem - *plen);
+            H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "post");
+        }
+        
+        if (rv == APR_SUCCESS) {
+            /* count the buffer again, now that we have read output */
+            status = add_buffered_data(stream, requested, plen, peos, &complete, pheaders);
+        }
+        else if (APR_STATUS_IS_EOF(rv)) {
             apr_bucket *eos = apr_bucket_eos_create(c->bucket_alloc);
-            APR_BRIGADE_INSERT_TAIL(stream->buffer, eos);
-            status = APR_SUCCESS;
+            APR_BRIGADE_INSERT_TAIL(stream->out_buffer, eos);
+            *peos = 1;
+        }
+        else if (APR_STATUS_IS_EAGAIN(rv)) {
+            /* we set this is the status of this call only if there
+             * is no buffered data, see check below */
+        }
+        else {
+            /* real error reading. Give this back directly, even though
+             * we may have something buffered. */
+            status = rv;
+        }
+    }
+    
+    if (status == APR_SUCCESS) {
+        if (*peos || *plen) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                          H2_STRM_MSG(stream, "prepare, len=%ld eos=%d"),
+                          (long)*plen, *peos);
+        }
+        else {
+            status = APR_EAGAIN;
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                          H2_STRM_MSG(stream, "prepare, no data"));
         }
-        else if (status == APR_EAGAIN) {
-            /* did not receive more, it's ok */
-            status = APR_SUCCESS;
-        }
-        *plen = requested;
-        h2_util_bb_avail(stream->buffer, plen, peos);
-    }
-    H2_STREAM_OUT_LOG(APLOG_TRACE2, stream, "h2_stream_out_prepare_post");
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, c,
-                  "h2_stream(%ld-%d): prepare, len=%ld eos=%d, trailers=%s",
-                  c->id, stream->id, (long)*plen, *peos,
-                  (stream->response && stream->response->trailers)? 
-                  "yes" : "no");
-    if (!*peos && !*plen && status == APR_SUCCESS) {
-        return APR_EAGAIN;
     }
     return status;
 }
 
+static int is_not_headers(apr_bucket *b)
+{
+    return !H2_BUCKET_IS_HEADERS(b);
+}
 
 apr_status_t h2_stream_read_to(h2_stream *stream, apr_bucket_brigade *bb, 
                                apr_off_t *plen, int *peos)
@@ -628,47 +946,28 @@ apr_status_t h2_stream_read_to(h2_stream
     if (stream->rst_error) {
         return APR_ECONNRESET;
     }
-    status = h2_append_brigade(bb, stream->buffer, plen, peos);
+    status = h2_append_brigade(bb, stream->out_buffer, plen, peos, is_not_headers);
     if (status == APR_SUCCESS && !*peos && !*plen) {
         status = APR_EAGAIN;
     }
     ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, c,
-                  "h2_stream(%ld-%d): read_to, len=%ld eos=%d",
-                  c->id, stream->id, (long)*plen, *peos);
+                  H2_STRM_MSG(stream, "read_to, len=%ld eos=%d"),
+                  (long)*plen, *peos);
     return status;
 }
 
 
-int h2_stream_input_is_open(const h2_stream *stream) 
-{
-    return input_open(stream);
-}
-
-int h2_stream_needs_submit(const h2_stream *stream)
-{
-    switch (stream->state) {
-        case H2_STREAM_ST_OPEN:
-        case H2_STREAM_ST_CLOSED_INPUT:
-        case H2_STREAM_ST_CLOSED_OUTPUT:
-        case H2_STREAM_ST_CLOSED:
-            return !stream->submitted;
-        default:
-            return 0;
-    }
-}
-
-apr_status_t h2_stream_submit_pushes(h2_stream *stream)
+apr_status_t h2_stream_submit_pushes(h2_stream *stream, h2_headers *response)
 {
     apr_status_t status = APR_SUCCESS;
     apr_array_header_t *pushes;
     int i;
     
-    pushes = h2_push_collect_update(stream, stream->request, 
-                                    h2_stream_get_response(stream));
+    pushes = h2_push_collect_update(stream, stream->request, response);
     if (pushes && !apr_is_empty_array(pushes)) {
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, stream->session->c,
-                      "h2_stream(%ld-%d): found %d push candidates",
-                      stream->session->id, stream->id, pushes->nelts);
+                      H2_STRM_MSG(stream, "found %d push candidates"),
+                      pushes->nelts);
         for (i = 0; i < pushes->nelts; ++i) {
             h2_push *push = APR_ARRAY_IDX(pushes, i, h2_push*);
             h2_stream *s = h2_session_push(stream->session, stream, push);
@@ -683,14 +982,13 @@ apr_status_t h2_stream_submit_pushes(h2_
 
 apr_table_t *h2_stream_get_trailers(h2_stream *stream)
 {
-    return stream->response? stream->response->trailers : NULL;
+    return NULL;
 }
 
-const h2_priority *h2_stream_get_priority(h2_stream *stream)
+const h2_priority *h2_stream_get_priority(h2_stream *stream, 
+                                          h2_headers *response)
 {
-    h2_response *response = h2_stream_get_response(stream);
-    
-    if (response && stream->request && stream->request->initiated_on) {
+    if (response && stream->initiated_on) {
         const char *ctype = apr_table_get(response->headers, "content-type");
         if (ctype) {
             /* FIXME: Not good enough, config needs to come from request->server */
@@ -700,3 +998,86 @@ const h2_priority *h2_stream_get_priorit
     return NULL;
 }
 
+int h2_stream_is_ready(h2_stream *stream)
+{
+    if (stream->has_response) {
+        return 1;
+    }
+    else if (stream->out_buffer && get_first_headers_bucket(stream->out_buffer)) {
+        return 1;
+    }
+    return 0;
+}
+
+int h2_stream_was_closed(const h2_stream *stream)
+{
+    switch (stream->state) {
+        case H2_SS_CLOSED:
+        case H2_SS_CLEANUP:
+            return 1;
+        default:
+            return 0;
+    }
+}
+
+apr_status_t h2_stream_in_consumed(h2_stream *stream, apr_off_t amount)
+{
+    h2_session *session = stream->session;
+    
+    if (amount > 0) {
+        apr_off_t consumed = amount;
+        
+        while (consumed > 0) {
+            int len = (consumed > INT_MAX)? INT_MAX : (int)consumed;
+            nghttp2_session_consume(session->ngh2, stream->id, len);
+            consumed -= len;
+        }
+
+#ifdef H2_NG2_LOCAL_WIN_SIZE
+        if (1) {
+            int cur_size = nghttp2_session_get_stream_local_window_size(
+                session->ngh2, stream->id);
+            int win = stream->in_window_size;
+            int thigh = win * 8/10;
+            int tlow = win * 2/10;
+            const int win_max = 2*1024*1024;
+            const int win_min = 32*1024;
+            
+            /* Work in progress, probably should add directives for these
+             * values once this stabilizes somewhat. The general idea is
+             * to adapt stream window sizes if the input window changes
+             * a) very quickly (< good RTT) from full to empty
+             * b) only a little bit (> bad RTT)
+             * where in a) it grows and in b) it shrinks again.
+             */
+            if (cur_size > thigh && amount > thigh && win < win_max) {
+                /* almost empty again with one reported consumption, how
+                 * long did this take? */
+                long ms = apr_time_msec(apr_time_now() - stream->in_last_write);
+                if (ms < 40) {
+                    win = H2MIN(win_max, win + (64*1024));
+                }
+            }
+            else if (cur_size < tlow && amount < tlow && win > win_min) {
+                /* staying full, for how long already? */
+                long ms = apr_time_msec(apr_time_now() - stream->in_last_write);
+                if (ms > 700) {
+                    win = H2MAX(win_min, win - (32*1024));
+                }
+            }
+            
+            if (win != stream->in_window_size) {
+                stream->in_window_size = win;
+                nghttp2_session_set_local_window_size(session->ngh2, 
+                        NGHTTP2_FLAG_NONE, stream->id, win);
+            } 
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, session->c,
+                          "h2_stream(%ld-%d): consumed %ld bytes, window now %d/%d",
+                          session->id, stream->id, (long)amount, 
+                          cur_size, stream->in_window_size);
+        }
+#endif
+    }
+    return APR_SUCCESS;   
+}
+
diff -up --new-file httpd-2.4.23/modules/http2/h2_stream.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_stream.h
--- httpd-2.4.23/modules/http2/h2_stream.h	2016-05-30 21:58:14.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_stream.h	2017-06-06 11:08:25.000000000 +0200
@@ -25,74 +25,132 @@
  * connection to the client. The h2_session writes to the h2_stream,
  * adding HEADERS and DATA and finally an EOS. When headers are done,
  * h2_stream is scheduled for handling, which is expected to produce
- * a h2_response.
+ * a response h2_headers at least.
  * 
- * The h2_response gives the HEADER frames to sent to the client, followed
- * by DATA frames read from the h2_stream until EOS is reached.
+ * The h2_headers may be followed by more h2_headers (interim responses) and
+ * by DATA frames read from the h2_stream until EOS is reached. Trailers
+ * are send when a last h2_headers is received. This always closes the stream
+ * output.
  */
 
 struct h2_mplx;
 struct h2_priority;
 struct h2_request;
-struct h2_response;
+struct h2_headers;
 struct h2_session;
-struct h2_sos;
+struct h2_task;
 struct h2_bucket_beam;
 
 typedef struct h2_stream h2_stream;
 
+typedef void h2_stream_state_cb(void *ctx, h2_stream *stream);
+typedef void h2_stream_event_cb(void *ctx, h2_stream *stream, 
+                                h2_stream_event_t ev);
+
+/**
+ * Callback structure for events and stream state transisitions
+ */
+typedef struct h2_stream_monitor {
+    void *ctx;
+    h2_stream_state_cb *on_state_enter;   /* called when a state is entered */
+    h2_stream_state_cb *on_state_invalid; /* called when an invalid state change
+                                             was detected */
+    h2_stream_event_cb *on_state_event;   /* called right before the given event
+                                             result in a new stream state */
+    h2_stream_event_cb *on_event;         /* called for events that do not 
+                                             trigger a state change */
+} h2_stream_monitor;
+
 struct h2_stream {
-    int id;                     /* http2 stream id */
-    h2_stream_state_t state;    /* http/2 state of this stream */
+    int id;                     /* http2 stream identifier */
+    int initiated_on;           /* initiating stream id (PUSH) or 0 */
+    apr_pool_t *pool;           /* the memory pool for this stream */
     struct h2_session *session; /* the session this stream belongs to */
+    h2_stream_state_t state;    /* state of this stream */
     
-    apr_pool_t *pool;           /* the memory pool for this stream */
-    struct h2_request *request; /* the request made in this stream */
-    struct h2_bucket_beam *input;
+    apr_time_t created;         /* when stream was created */
+    
+    const struct h2_request *request; /* the request made in this stream */
+    struct h2_request *rtmp;    /* request being assembled */
+    apr_table_t *trailers;      /* optional incoming trailers */
     int request_headers_added;  /* number of request headers added */
     
-    struct h2_response *response;
+    struct h2_bucket_beam *input;
+    apr_bucket_brigade *in_buffer;
+    int in_window_size;
+    apr_time_t in_last_write;
+    
     struct h2_bucket_beam *output;
-    apr_bucket_brigade *buffer;
-    apr_bucket_brigade *tmp;
-    apr_array_header_t *files;  /* apr_file_t* we collected during I/O */
+    apr_bucket_brigade *out_buffer;
+    apr_size_t max_mem;         /* maximum amount of data buffered */
 
     int rst_error;              /* stream error for RST_STREAM */
     unsigned int aborted   : 1; /* was aborted */
-    unsigned int suspended : 1; /* DATA sending has been suspended */
     unsigned int scheduled : 1; /* stream has been scheduled */
-    unsigned int started   : 1; /* stream has started processing */
-    unsigned int submitted : 1; /* response HEADER has been sent */
+    unsigned int has_response : 1; /* response headers are known */
+    unsigned int input_eof : 1; /* no more request data coming */
+    unsigned int out_checked : 1; /* output eof was double checked */
+    unsigned int push_policy;   /* which push policy to use for this request */
+    
+    struct h2_task *task;       /* assigned task to fullfill request */
     
-    apr_off_t input_remaining;  /* remaining bytes on input as advertised via content-length */
-    apr_off_t data_frames_sent; /* # of DATA frames sent out for this stream */
+    const h2_priority *pref_priority; /* preferred priority for this stream */
+    apr_off_t out_data_frames;  /* # of DATA frames sent */
+    apr_off_t out_data_octets;  /* # of DATA octets (payload) sent */
+    apr_off_t in_data_frames;   /* # of DATA frames received */
+    apr_off_t in_data_octets;   /* # of DATA octets (payload) received */
+    
+    h2_stream_monitor *monitor; /* optional monitor for stream states */
 };
 
 
 #define H2_STREAM_RST(s, def)    (s->rst_error? s->rst_error : (def))
 
 /**
- * Create a stream in OPEN state.
+ * Create a stream in H2_SS_IDLE state.
  * @param id      the stream identifier
  * @param pool    the memory pool to use for this stream
  * @param session the session this stream belongs to
+ * @param monitor an optional monitor to be called for events and 
+ *                state transisitions
+ * @param initiated_on the id of the stream this one was initiated on (PUSH)
+ *
  * @return the newly opened stream
  */
-h2_stream *h2_stream_open(int id, apr_pool_t *pool, struct h2_session *session,
-                          int initiated_on, const struct h2_request *req);
+h2_stream *h2_stream_create(int id, apr_pool_t *pool, 
+                            struct h2_session *session,
+                            h2_stream_monitor *monitor,
+                            int initiated_on);
 
 /**
- * Cleanup any resources still held by the stream, called by last bucket.
+ * Destroy memory pool if still owned by the stream.
  */
-void h2_stream_eos_destroy(h2_stream *stream);
+void h2_stream_destroy(h2_stream *stream);
 
 /**
- * Destroy memory pool if still owned by the stream.
+ * Prepare the stream so that processing may start.
+ * 
+ * This is the time to allocated resources not needed before.
+ * 
+ * @param stream the stream to prep 
  */
-void h2_stream_destroy(h2_stream *stream);
+apr_status_t h2_stream_prep_processing(h2_stream *stream);
+
+/*
+ * Set a new monitor for this stream, replacing any existing one. Can
+ * be called with NULL to have no monitor installed.
+ */
+void h2_stream_set_monitor(h2_stream *stream, h2_stream_monitor *monitor);
+
+/**
+ * Dispatch (handle) an event on the given stream.
+ * @param stream  the streama the event happened on
+ * @param ev      the type of event
+ */
+void h2_stream_dispatch(h2_stream *stream, h2_stream_event_t ev);
 
 /**
- * Removes stream from h2_session and destroys it.
+ * Cleanup references into requst processing.
  *
  * @param stream the stream to cleanup
  */
@@ -107,14 +165,30 @@ void h2_stream_cleanup(h2_stream *stream
  */
 apr_pool_t *h2_stream_detach_pool(h2_stream *stream);
 
+/**
+ * Notify the stream that amount bytes have been consumed of its input
+ * since the last invocation of this method (delta amount).
+ */
+apr_status_t h2_stream_in_consumed(h2_stream *stream, apr_off_t amount);
+
+/**
+ * Set complete stream headers from given h2_request.
+ * 
+ * @param stream stream to write request to
+ * @param r the request with all the meta data
+ * @param eos != 0 iff stream input is closed
+ */
+void h2_stream_set_request(h2_stream *stream, const h2_request *r);
 
 /**
- * Initialize stream->request with the given request_rec.
+ * Set complete stream header from given request_rec.
  * 
  * @param stream stream to write request to
  * @param r the request with all the meta data
+ * @param eos != 0 iff stream input is closed
  */
-apr_status_t h2_stream_set_request(h2_stream *stream, request_rec *r);
+apr_status_t h2_stream_set_request_rec(h2_stream *stream, 
+                                       request_rec *r, int eos);
 
 /*
  * Add a HTTP/2 header (including pseudo headers) or trailer 
@@ -130,22 +204,21 @@ apr_status_t h2_stream_add_header(h2_str
                                   const char *name, size_t nlen,
                                   const char *value, size_t vlen);
 
-/**
- * Closes the stream's input.
- *
- * @param stream stream to close intput of
- */
-apr_status_t h2_stream_close_input(h2_stream *stream);
+apr_status_t h2_stream_send_frame(h2_stream *stream, int frame_type, int flags);
+apr_status_t h2_stream_recv_frame(h2_stream *stream, int frame_type, int flags);
 
 /*
- * Write a chunk of DATA to the stream.
+ * Process a frame of received DATA.
  *
  * @param stream stream to write the data to
+ * @param flags the frame flags
  * @param data the beginning of the bytes to write
  * @param len the number of bytes to write
  */
-apr_status_t h2_stream_write_data(h2_stream *stream,
-                                  const char *data, size_t len, int eos);
+apr_status_t h2_stream_recv_DATA(h2_stream *stream, uint8_t flags,
+                                 const uint8_t *data, size_t len);
+
+apr_status_t h2_stream_flush_input(h2_stream *stream);
 
 /**
  * Reset the stream. Stream write/reads will return errors afterwards.
@@ -156,44 +229,14 @@ apr_status_t h2_stream_write_data(h2_str
 void h2_stream_rst(h2_stream *stream, int error_code);
 
 /**
- * Schedule the stream for execution. All header information must be
- * present. Use the given priority comparision callback to determine 
- * order in queued streams.
- * 
- * @param stream the stream to schedule
- * @param eos    != 0 iff no more input will arrive
- * @param cmp    priority comparision
- * @param ctx    context for comparision
- */
-apr_status_t h2_stream_schedule(h2_stream *stream, int eos, int push_enabled,
-                                h2_stream_pri_cmp *cmp, void *ctx);
-
-/**
- * Determine if stream has been scheduled already.
+ * Determine if stream was closed already. This is true for
+ * states H2_SS_CLOSED, H2_SS_CLEANUP. But not true
+ * for H2_SS_CLOSED_L and H2_SS_CLOSED_R.
+ *
  * @param stream the stream to check on
- * @return != 0 iff stream has been scheduled
- */
-int h2_stream_is_scheduled(const h2_stream *stream);
-
-struct h2_response *h2_stream_get_response(h2_stream *stream);
-
-/**
- * Set the response for this stream. Invoked when all meta data for
- * the stream response has been collected.
- * 
- * @param stream the stream to set the response for
- * @param response the response data for the stream
- * @param bb bucket brigade with output data for the stream. Optional,
- *        may be incomplete.
+ * @return != 0 iff stream has been closed
  */
-apr_status_t h2_stream_set_response(h2_stream *stream, 
-                                    struct h2_response *response,
-                                    struct h2_bucket_beam *output);
-
-/**
- * Set the HTTP error status as response.
- */
-apr_status_t h2_stream_set_error(h2_stream *stream, int http_status);
+int h2_stream_was_closed(const h2_stream *stream);
 
 /**
  * Do a speculative read on the stream output to determine the 
@@ -204,12 +247,13 @@ apr_status_t h2_stream_set_error(h2_stre
  *        may be read without blocking
  * @param peos (out) != 0 iff end of stream will be reached when reading plen
  *        bytes (out value).
+ * @param presponse (out) the response of one became available
  * @return APR_SUCCESS if out information was computed successfully.
  *         APR_EAGAIN if not data is available and end of stream has not been
  *         reached yet.
  */
-apr_status_t h2_stream_out_prepare(h2_stream *stream, 
-                                   apr_off_t *plen, int *peos);
+apr_status_t h2_stream_out_prepare(h2_stream *stream, apr_off_t *plen, 
+                                   int *peos, h2_headers **presponse);
 
 /**
  * Read a maximum number of bytes into the bucket brigade.
@@ -237,44 +281,34 @@ apr_status_t h2_stream_read_to(h2_stream
 apr_table_t *h2_stream_get_trailers(h2_stream *stream);
 
 /**
- * Set the suspended state of the stream.
- * @param stream the stream to change state on
- * @param suspended boolean value if stream is suspended
+ * Submit any server push promises on this stream and schedule
+ * the tasks connection with these.
+ *
+ * @param stream the stream for which to submit
  */
-void h2_stream_set_suspended(h2_stream *stream, int suspended);
+apr_status_t h2_stream_submit_pushes(h2_stream *stream, h2_headers *response);
 
 /**
- * Check if the stream has been suspended.
- * @param stream the stream to check
- * @return != 0 iff stream is suspended.
+ * Get priority information set for this stream.
  */
-int h2_stream_is_suspended(const h2_stream *stream);
+const struct h2_priority *h2_stream_get_priority(h2_stream *stream, 
+                                                 h2_headers *response);
 
 /**
- * Check if the stream has open input.
- * @param stream the stream to check
- * @return != 0 iff stream has open input.
+ * Return a textual representation of the stream state as in RFC 7540
+ * nomenclator, all caps, underscores.
  */
-int h2_stream_input_is_open(const h2_stream *stream);
+const char *h2_stream_state_str(h2_stream *stream);
 
 /**
- * Check if the stream has not submitted a response or RST yet.
+ * Determine if stream is ready for submitting a response or a RST
  * @param stream the stream to check
- * @return != 0 iff stream has not submitted a response or RST.
  */
-int h2_stream_needs_submit(const h2_stream *stream);
+int h2_stream_is_ready(h2_stream *stream);
 
-/**
- * Submit any server push promises on this stream and schedule
- * the tasks connection with these.
- *
- * @param stream the stream for which to submit
- */
-apr_status_t h2_stream_submit_pushes(h2_stream *stream);
+#define H2_STRM_MSG(s, msg)     \
+    "h2_stream(%ld-%d,%s): "msg, s->session->id, s->id, h2_stream_state_str(s)
 
-/**
- * Get priority information set for this stream.
- */
-const struct h2_priority *h2_stream_get_priority(h2_stream *stream);
+#define H2_STRM_LOG(aplogno, s, msg)    aplogno H2_STRM_MSG(s, msg)
 
 #endif /* defined(__mod_h2__h2_stream__) */
diff -up --new-file httpd-2.4.23/modules/http2/h2_stream_set.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_stream_set.c
--- httpd-2.4.23/modules/http2/h2_stream_set.c	2015-11-20 16:13:11.000000000 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_stream_set.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,145 +0,0 @@
-/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <assert.h>
-#include <stddef.h>
-
-#include <apr_hash.h>
-#include <apr_strings.h>
-
-#include <httpd.h>
-#include <http_log.h>
-
-#include "h2_private.h"
-#include "h2_stream.h"
-#include "h2_stream_set.h"
-
-
-struct h2_stream_set {
-    apr_hash_t *hash;
-};
-
-static unsigned int stream_hash(const char *key, apr_ssize_t *klen)
-{
-    return (unsigned int)(*((int*)key));
-}
-
-h2_stream_set *h2_stream_set_create(apr_pool_t *pool, int max)
-{
-    h2_stream_set *sp = apr_pcalloc(pool, sizeof(h2_stream_set));
-    sp->hash = apr_hash_make_custom(pool, stream_hash);
-
-    return sp;
-}
-
-void h2_stream_set_destroy(h2_stream_set *sp)
-{
-    (void)sp;
-}
-
-h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id)
-{
-    return apr_hash_get(sp->hash, &stream_id, sizeof(stream_id));
-}
-
-void h2_stream_set_add(h2_stream_set *sp, h2_stream *stream)
-{
-    apr_hash_set(sp->hash, &stream->id, sizeof(stream->id), stream);
-}
-
-void h2_stream_set_remove(h2_stream_set *sp, int stream_id)
-{
-    apr_hash_set(sp->hash, &stream_id, sizeof(stream_id), NULL);
-}
-
-int h2_stream_set_is_empty(h2_stream_set *sp)
-{
-    return apr_hash_count(sp->hash) == 0;
-}
-
-apr_size_t h2_stream_set_size(h2_stream_set *sp)
-{
-    return apr_hash_count(sp->hash);
-}
-
-typedef struct {
-    h2_stream_set_iter_fn *iter;
-    void *ctx;
-} iter_ctx;
-
-static int hash_iter(void *ctx, const void *key, apr_ssize_t klen, 
-                     const void *val)
-{
-    iter_ctx *ictx = ctx;
-    return ictx->iter(ictx->ctx, (h2_stream*)val);
-}
-
-void h2_stream_set_iter(h2_stream_set *sp,
-                        h2_stream_set_iter_fn *iter, void *ctx)
-{
-    iter_ctx ictx;
-    ictx.iter = iter;
-    ictx.ctx = ctx;
-    apr_hash_do(hash_iter, &ictx, sp->hash);
-}
-
-static int unsubmitted_iter(void *ctx, h2_stream *stream)
-{
-    if (h2_stream_needs_submit(stream)) {
-        *((int *)ctx) = 1;
-        return 0;
-    }
-    return 1;
-}
-
-int h2_stream_set_has_unsubmitted(h2_stream_set *sp)
-{
-    int has_unsubmitted = 0;
-    h2_stream_set_iter(sp, unsubmitted_iter, &has_unsubmitted);
-    return has_unsubmitted;
-}
-
-static int input_open_iter(void *ctx, h2_stream *stream)
-{
-    if (h2_stream_input_is_open(stream)) {
-        *((int *)ctx) = 1;
-        return 0;
-    }
-    return 1;
-}
-
-int h2_stream_set_has_open_input(h2_stream_set *sp)
-{
-    int has_input_open = 0;
-    h2_stream_set_iter(sp, input_open_iter, &has_input_open);
-    return has_input_open;
-}
-
-static int suspended_iter(void *ctx, h2_stream *stream)
-{
-    if (h2_stream_is_suspended(stream)) {
-        *((int *)ctx) = 1;
-        return 0;
-    }
-    return 1;
-}
-
-int h2_stream_set_has_suspended(h2_stream_set *sp)
-{
-    int has_suspended = 0;
-    h2_stream_set_iter(sp, suspended_iter, &has_suspended);
-    return has_suspended;
-}
-
diff -up --new-file httpd-2.4.23/modules/http2/h2_stream_set.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_stream_set.h
--- httpd-2.4.23/modules/http2/h2_stream_set.h	2015-11-20 16:13:11.000000000 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_stream_set.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,51 +0,0 @@
-/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __mod_h2__h2_stream_set__
-#define __mod_h2__h2_stream_set__
-
-/**
- * A set of h2_stream instances. Allows lookup by stream id
- * and other criteria.
- */
-
-typedef h2_stream *h2_stream_set_match_fn(void *ctx, h2_stream *stream);
-typedef int h2_stream_set_iter_fn(void *ctx, h2_stream *stream);
-
-typedef struct h2_stream_set h2_stream_set;
-
-
-h2_stream_set *h2_stream_set_create(apr_pool_t *pool, int max);
-
-void h2_stream_set_destroy(h2_stream_set *sp);
-
-void h2_stream_set_add(h2_stream_set *sp, h2_stream *stream);
-
-h2_stream *h2_stream_set_get(h2_stream_set *sp, int stream_id);
-
-void h2_stream_set_remove(h2_stream_set *sp, int stream_id);
-
-void h2_stream_set_iter(h2_stream_set *sp,
-                        h2_stream_set_iter_fn *iter, void *ctx);
-
-int h2_stream_set_is_empty(h2_stream_set *sp);
-
-apr_size_t h2_stream_set_size(h2_stream_set *sp);
-
-int h2_stream_set_has_unsubmitted(h2_stream_set *sp);
-int h2_stream_set_has_open_input(h2_stream_set *sp);
-int h2_stream_set_has_suspended(h2_stream_set *sp);
-
-#endif /* defined(__mod_h2__h2_stream_set__) */
diff -up --new-file httpd-2.4.23/modules/http2/h2_switch.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_switch.c
--- httpd-2.4.23/modules/http2/h2_switch.c	2016-02-10 00:09:24.000000000 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_switch.c	2017-07-04 14:34:15.000000000 +0200
@@ -55,6 +55,10 @@ static int h2_protocol_propose(conn_rec
     const char **protos = is_tls? h2_tls_protos : h2_clear_protos;
     
     (void)s;
+    if (!h2_mpm_supported()) {
+        return DECLINED;
+    }
+    
     if (strcmp(AP_PROTOCOL_HTTP1, ap_get_protocol(c))) {
         /* We do not know how to switch from anything else but http/1.1.
          */
@@ -127,6 +131,10 @@ static int h2_protocol_switch(conn_rec *
     const char **p = protos;
     
     (void)s;
+    if (!h2_mpm_supported()) {
+        return DECLINED;
+    }
+
     while (*p) {
         if (!strcmp(*p, protocol)) {
             found = 1;
@@ -160,6 +168,7 @@ static int h2_protocol_switch(conn_rec *
             if (status != APR_SUCCESS) {
                 ap_log_rerror(APLOG_MARK, APLOG_DEBUG, status, r, APLOGNO(03088)
                               "session setup");
+                h2_ctx_clear(c);
                 return status;
             }
             
diff -up --new-file httpd-2.4.23/modules/http2/h2_switch.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_switch.h
--- httpd-2.4.23/modules/http2/h2_switch.h	2015-09-07 19:37:19.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_switch.h	2016-08-25 14:48:18.000000000 +0200
@@ -17,7 +17,7 @@
 #define __mod_h2__h2_switch__
 
 /*
- * One time, post config intialization.
+ * One time, post config initialization.
  */
 apr_status_t h2_switch_init(apr_pool_t *pool, server_rec *s);
 
diff -up --new-file httpd-2.4.23/modules/http2/h2_task.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_task.c
--- httpd-2.4.23/modules/http2/h2_task.c	2016-06-22 15:18:13.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_task.c	2017-10-13 10:37:45.000000000 +0200
@@ -17,7 +17,6 @@
 #include <stddef.h>
 
 #include <apr_atomic.h>
-#include <apr_thread_cond.h>
 #include <apr_strings.h>
 
 #include <httpd.h>
@@ -42,12 +41,27 @@
 #include "h2_h2.h"
 #include "h2_mplx.h"
 #include "h2_request.h"
+#include "h2_headers.h"
 #include "h2_session.h"
 #include "h2_stream.h"
 #include "h2_task.h"
-#include "h2_worker.h"
 #include "h2_util.h"
 
+static void H2_TASK_OUT_LOG(int lvl, h2_task *task, apr_bucket_brigade *bb, 
+                            const char *tag)
+{
+    if (APLOG_C_IS_LEVEL(task->c, lvl)) {
+        conn_rec *c = task->c;
+        char buffer[4 * 1024];
+        const char *line = "(null)";
+        apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]);
+        
+        len = h2_util_bb_print(buffer, bmax, tag, "", bb);
+        ap_log_cerror(APLOG_MARK, lvl, 0, c, "bb_dump(%s): %s", 
+                      task->id, len? buffer : line);
+    }
+}
+
 /*******************************************************************************
  * task input handling
  ******************************************************************************/
@@ -59,107 +73,186 @@ static int input_ser_header(void *ctx, c
     return 1;
 }
 
-static void make_chunk(h2_task *task, apr_bucket_brigade *bb, 
-                       apr_bucket *first, apr_uint64_t chunk_len, 
-                       apr_bucket *tail)
-{
-    /* Surround the buckets [first, tail[ with new buckets carrying the
-     * HTTP/1.1 chunked encoding format. If tail is NULL, the chunk extends
-     * to the end of the brigade. */
-    char buffer[128];
-    apr_bucket *c;
-    int len;
-    
-    len = apr_snprintf(buffer, H2_ALEN(buffer), 
-                       "%"APR_UINT64_T_HEX_FMT"\r\n", chunk_len);
-    c = apr_bucket_heap_create(buffer, len, NULL, bb->bucket_alloc);
-    APR_BUCKET_INSERT_BEFORE(first, c);
-    c = apr_bucket_heap_create("\r\n", 2, NULL, bb->bucket_alloc);
-    if (tail) {
-        APR_BUCKET_INSERT_BEFORE(tail, c);
-    }
-    else {
-        APR_BRIGADE_INSERT_TAIL(bb, c);
-    }
+/*******************************************************************************
+ * task output handling
+ ******************************************************************************/
+
+static apr_status_t open_output(h2_task *task)
+{
+    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03348)
+                  "h2_task(%s): open output to %s %s %s",
+                  task->id, task->request->method, 
+                  task->request->authority, 
+                  task->request->path);
+    task->output.opened = 1;
+    return h2_mplx_out_open(task->mplx, task->stream_id, task->output.beam);
 }
 
-static apr_status_t input_handle_eos(h2_task *task, request_rec *r, 
-                                     apr_bucket *b)
+static apr_status_t send_out(h2_task *task, apr_bucket_brigade* bb, int block)
 {
-    apr_status_t status = APR_SUCCESS;
-    apr_bucket_brigade *bb = task->input.bb;
-    apr_table_t *t = task->request? task->request->trailers : NULL;
+    apr_off_t written, left;
+    apr_status_t status;
 
-    if (task->input.chunked) {
-        task->input.tmp = apr_brigade_split_ex(bb, b, task->input.tmp);
-        if (t && !apr_is_empty_table(t)) {
-            status = apr_brigade_puts(bb, NULL, NULL, "0\r\n");
-            apr_table_do(input_ser_header, task, t, NULL);
-            status = apr_brigade_puts(bb, NULL, NULL, "\r\n");
-        }
-        else {
-            status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
+    apr_brigade_length(bb, 0, &written);
+    H2_TASK_OUT_LOG(APLOG_TRACE2, task, bb, "h2_task send_out");
+    h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "send_out(before)");
+    /* engines send unblocking */
+    status = h2_beam_send(task->output.beam, bb, 
+                          block? APR_BLOCK_READ : APR_NONBLOCK_READ);
+    h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "send_out(after)");
+    
+    if (APR_STATUS_IS_EAGAIN(status)) {
+        apr_brigade_length(bb, 0, &left);
+        written -= left;
+        status = APR_SUCCESS;
+    }
+    if (status == APR_SUCCESS) {
+        if (h2_task_logio_add_bytes_out) {
+            h2_task_logio_add_bytes_out(task->c, written);
         }
-        APR_BRIGADE_CONCAT(bb, task->input.tmp);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, task->c, 
+                      "h2_task(%s): send_out done", task->id);
     }
-    else if (r && t && !apr_is_empty_table(t)){
-        /* trailers passed in directly. */
-        apr_table_overlap(r->trailers_in, t, APR_OVERLAP_TABLES_SET);
+    else {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, task->c,
+                      "h2_task(%s): send_out (%ld bytes)", 
+                      task->id, (long)written);
     }
-    task->input.eos_written = 1;
     return status;
 }
 
-static apr_status_t input_append_eos(h2_task *task, request_rec *r)
+/* Bring the data from the brigade (which represents the result of the
+ * request_rec out filter chain) into the h2_mplx for further sending
+ * on the master connection. 
+ */
+static apr_status_t slave_out(h2_task *task, ap_filter_t* f, 
+                              apr_bucket_brigade* bb)
 {
-    apr_status_t status = APR_SUCCESS;
-    apr_bucket_brigade *bb = task->input.bb;
-    apr_table_t *t = task->request? task->request->trailers : NULL;
+    apr_bucket *b;
+    apr_status_t rv = APR_SUCCESS;
+    int flush = 0, blocking;
+    
+    if (task->frozen) {
+        h2_util_bb_log(task->c, task->stream_id, APLOG_TRACE2,
+                       "frozen task output write, ignored", bb);
+        while (!APR_BRIGADE_EMPTY(bb)) {
+            b = APR_BRIGADE_FIRST(bb);
+            if (AP_BUCKET_IS_EOR(b)) {
+                APR_BUCKET_REMOVE(b);
+                task->eor = b;
+            }
+            else {
+                apr_bucket_delete(b);
+            }
+        }
+        return APR_SUCCESS;
+    }
 
-    if (task->input.chunked) {
-        if (t && !apr_is_empty_table(t)) {
-            status = apr_brigade_puts(bb, NULL, NULL, "0\r\n");
-            apr_table_do(input_ser_header, task, t, NULL);
-            status = apr_brigade_puts(bb, NULL, NULL, "\r\n");
+send:
+    /* we send block once we opened the output, so someone is there
+     * reading it *and* the task is not assigned to a h2_req_engine */
+    blocking = (!task->assigned && task->output.opened);
+    for (b = APR_BRIGADE_FIRST(bb);
+         b != APR_BRIGADE_SENTINEL(bb);
+         b = APR_BUCKET_NEXT(b)) {
+        if (APR_BUCKET_IS_FLUSH(b) || APR_BUCKET_IS_EOS(b) || AP_BUCKET_IS_EOR(b)) {
+            flush = 1;
+            break;
         }
-        else {
-            status = apr_brigade_puts(bb, NULL, NULL, "0\r\n\r\n");
+    }
+    
+    if (task->output.bb && !APR_BRIGADE_EMPTY(task->output.bb)) {
+        /* still have data buffered from previous attempt.
+         * setaside and append new data and try to pass the complete data */
+        if (!APR_BRIGADE_EMPTY(bb)) {
+            if (APR_SUCCESS != (rv = ap_save_brigade(f, &task->output.bb, &bb, task->pool))) {
+                goto out;
+            }
         }
+        rv = send_out(task, task->output.bb, blocking);
     }
-    else if (r && t && !apr_is_empty_table(t)){
-        /* trailers passed in directly. */
-        apr_table_overlap(r->trailers_in, t, APR_OVERLAP_TABLES_SET);
+    else {
+        /* no data buffered previously, pass brigade directly */
+        rv = send_out(task, bb, blocking);
+
+        if (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(bb)) {
+            /* output refused to buffer it all, time to open? */
+            if (!task->output.opened && APR_SUCCESS == (rv = open_output(task))) {
+                /* Make another attempt to send the data. With the output open,
+                 * the call might be blocking and send all data, so we do not need
+                 * to save the brigade */
+                goto send;
+            }
+            else if (blocking && flush) {
+                /* Need to keep on doing this. */
+                goto send;
+            }
+            
+            if (APR_SUCCESS == rv) {
+                /* could not write all, buffer the rest */
+                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, rv, task->c, APLOGNO(03405)
+                              "h2_slave_out(%s): saving brigade", task->id);
+                ap_assert(NULL);
+                rv = ap_save_brigade(f, &task->output.bb, &bb, task->pool);
+                flush = 1;
+            }
+        }
     }
-    APR_BRIGADE_INSERT_TAIL(bb, apr_bucket_eos_create(bb->bucket_alloc));
-    task->input.eos_written = 1;
-    return status;
+    
+    if (APR_SUCCESS == rv && !task->output.opened && flush) {
+        /* got a flush or could not write all, time to tell someone to read */
+        rv = open_output(task);
+    }
+out:
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE2, rv, task->c, 
+                  "h2_slave_out(%s): slave_out leave", task->id);    
+    return rv;
+}
+
+static apr_status_t output_finish(h2_task *task)
+{
+    if (!task->output.opened) {
+        return open_output(task);
+    }
+    return APR_SUCCESS;
 }
 
-static apr_status_t input_read(h2_task *task, ap_filter_t* f,
-                               apr_bucket_brigade* bb, ap_input_mode_t mode,
-                               apr_read_type_e block, apr_off_t readbytes)
+/*******************************************************************************
+ * task slave connection filters
+ ******************************************************************************/
+
+static apr_status_t h2_filter_slave_in(ap_filter_t* f,
+                                       apr_bucket_brigade* bb,
+                                       ap_input_mode_t mode,
+                                       apr_read_type_e block,
+                                       apr_off_t readbytes)
 {
+    h2_task *task;
     apr_status_t status = APR_SUCCESS;
-    apr_bucket *b, *next, *first_data;
-    apr_off_t bblen = 0;
+    apr_bucket *b, *next;
+    apr_off_t bblen;
+    const int trace1 = APLOGctrace1(f->c);
+    apr_size_t rmax = ((readbytes <= APR_SIZE_MAX)? 
+                       (apr_size_t)readbytes : APR_SIZE_MAX);
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                  "h2_task(%s): read, mode=%d, block=%d, readbytes=%ld", 
-                  task->id, mode, block, (long)readbytes);
+    task = h2_ctx_cget_task(f->c);
+    ap_assert(task);
+
+    if (trace1) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+                      "h2_slave_in(%s): read, mode=%d, block=%d, readbytes=%ld", 
+                      task->id, mode, block, (long)readbytes);
+    }
     
     if (mode == AP_MODE_INIT) {
         return ap_get_brigade(f->c->input_filters, bb, mode, block, readbytes);
     }
     
-    if (f->c->aborted || !task->request) {
+    if (f->c->aborted) {
         return APR_ECONNABORTED;
     }
     
     if (!task->input.bb) {
-        if (!task->input.eos_written) {
-            input_append_eos(task, f->r);
-            return APR_SUCCESS;
-        }
         return APR_EOF;
     }
     
@@ -173,25 +266,25 @@ static apr_status_t input_read(h2_task *
         } 
     }
     
-    while (APR_BRIGADE_EMPTY(task->input.bb) && !task->input.eos) {
+    while (APR_BRIGADE_EMPTY(task->input.bb)) {
         /* Get more input data for our request. */
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
-                      "h2_task(%s): get more data from mplx, block=%d, "
-                      "readbytes=%ld, queued=%ld",
-                      task->id, block, (long)readbytes, (long)bblen);
-        
-        /* Override the block mode we get called with depending on the input's
-         * setting. */
+        if (trace1) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+                          "h2_slave_in(%s): get more data from mplx, block=%d, "
+                          "readbytes=%ld", task->id, block, (long)readbytes);
+        }
         if (task->input.beam) {
             status = h2_beam_receive(task->input.beam, task->input.bb, block, 
-                                     H2MIN(readbytes, 32*1024));
+                                     128*1024);
         }
         else {
             status = APR_EOF;
         }
         
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
-                      "h2_task(%s): read returned", task->id);
+        if (trace1) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
+                          "h2_slave_in(%s): read returned", task->id);
+        }
         if (APR_STATUS_IS_EAGAIN(status) 
             && (mode == AP_MODE_GETLINE || block == APR_BLOCK_READ)) {
             /* chunked input handling does not seem to like it if we
@@ -200,69 +293,39 @@ static apr_status_t input_read(h2_task *
             status = APR_SUCCESS;
         }
         else if (APR_STATUS_IS_EOF(status)) {
-            task->input.eos = 1;
+            break;
         }
         else if (status != APR_SUCCESS) {
             return status;
         }
-        
-        /* Inspect the buckets received, detect EOS and apply
-         * chunked encoding if necessary */
-        h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, 
-                       "input.beam recv raw", task->input.bb);
-        first_data = NULL;
-        bblen = 0;
-        for (b = APR_BRIGADE_FIRST(task->input.bb); 
-             b != APR_BRIGADE_SENTINEL(task->input.bb); b = next) {
-            next = APR_BUCKET_NEXT(b);
-            if (APR_BUCKET_IS_METADATA(b)) {
-                if (first_data && task->input.chunked) {
-                    make_chunk(task, task->input.bb, first_data, bblen, b);
-                    first_data = NULL;
-                    bblen = 0;
-                }
-                if (APR_BUCKET_IS_EOS(b)) {
-                    task->input.eos = 1;
-                    input_handle_eos(task, f->r, b);
-                    h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, 
-                                   "input.bb after handle eos", 
-                                   task->input.bb);
-                }
-            }
-            else if (b->length == 0) {
-                apr_bucket_delete(b);
-            } 
-            else {
-                if (!first_data) {
-                    first_data = b;
-                }
-                bblen += b->length;
-            }    
-        }
-        if (first_data && task->input.chunked) {
-            make_chunk(task, task->input.bb, first_data, bblen, NULL);
-        }            
-        
+
+        if (trace1) {
+            h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, 
+                        "input.beam recv raw", task->input.bb);
+        }
         if (h2_task_logio_add_bytes_in) {
+            apr_brigade_length(bb, 0, &bblen);
             h2_task_logio_add_bytes_in(f->c, bblen);
         }
     }
     
-    if (task->input.eos) {
-        if (!task->input.eos_written) {
-            input_append_eos(task, f->r);
-        }
-        if (APR_BRIGADE_EMPTY(task->input.bb)) {
-            return APR_EOF;
-        }
+    /* Nothing there, no more data to get. Return APR_EAGAIN on
+     * speculative reads, this is ap_check_pipeline()'s trick to
+     * see if the connection needs closing. */
+    if (status == APR_EOF && APR_BRIGADE_EMPTY(task->input.bb)) {
+        return (mode == AP_MODE_SPECULATIVE)? APR_EAGAIN : APR_EOF;
     }
 
-    h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, 
-                   "task_input.bb", task->input.bb);
+    if (trace1) {
+        h2_util_bb_log(f->c, task->stream_id, APLOG_TRACE2, 
+                    "task_input.bb", task->input.bb);
+    }
            
     if (APR_BRIGADE_EMPTY(task->input.bb)) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
-                      "h2_task(%s): no data", task->id);
+        if (trace1) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, f->c,
+                          "h2_slave_in(%s): no data", task->id);
+        }
         return (block == APR_NONBLOCK_READ)? APR_EAGAIN : APR_EOF;
     }
     
@@ -271,10 +334,10 @@ static apr_status_t input_read(h2_task *
         APR_BRIGADE_CONCAT(bb, task->input.bb);
     }
     else if (mode == AP_MODE_READBYTES) {
-        status = h2_brigade_concat_length(bb, task->input.bb, readbytes);
+        status = h2_brigade_concat_length(bb, task->input.bb, rmax);
     }
     else if (mode == AP_MODE_SPECULATIVE) {
-        status = h2_brigade_copy_length(bb, task->input.bb, readbytes);
+        status = h2_brigade_copy_length(bb, task->input.bb, rmax);
     }
     else if (mode == AP_MODE_GETLINE) {
         /* we are reading a single LF line, e.g. the HTTP headers. 
@@ -287,249 +350,72 @@ static apr_status_t input_read(h2_task *
             apr_size_t len = sizeof(buffer)-1;
             apr_brigade_flatten(bb, buffer, &len);
             buffer[len] = 0;
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
-                          "h2_task(%s): getline: %s",
-                          task->id, buffer);
+            if (trace1) {
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
+                              "h2_slave_in(%s): getline: %s",
+                              task->id, buffer);
+            }
         }
     }
     else {
         /* Hmm, well. There is mode AP_MODE_EATCRLF, but we chose not
          * to support it. Seems to work. */
         ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOTIMPL, f->c,
-                      APLOGNO(02942) 
-                      "h2_task, unsupported READ mode %d", mode);
+                      APLOGNO(03472) 
+                      "h2_slave_in(%s), unsupported READ mode %d", 
+                      task->id, mode);
         status = APR_ENOTIMPL;
     }
     
-    if (APLOGctrace1(f->c)) {
+    if (trace1) {
         apr_brigade_length(bb, 0, &bblen);
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, f->c,
-                      "h2_task(%s): return %ld data bytes",
-                      task->id, (long)bblen);
+                      "h2_slave_in(%s): %ld data bytes", task->id, (long)bblen);
     }
     return status;
 }
 
-/*******************************************************************************
- * task output handling
- ******************************************************************************/
-
-static apr_status_t open_response(h2_task *task)
+static apr_status_t h2_filter_slave_output(ap_filter_t* filter,
+                                           apr_bucket_brigade* brigade)
 {
-    h2_response *response;
-    response = h2_from_h1_get_response(task->output.from_h1);
-    if (!response) {
-        /* This happens currently when ap_die(status, r) is invoked
-         * by a read request filter. */
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03204)
-                      "h2_task(%s): write without response for %s %s %s",
-                      task->id, 
-                      task->request->method, 
-                      task->request->authority, 
-                      task->request->path);
-        task->c->aborted = 1;
-        return APR_ECONNABORTED;
-    }
-    
-    if (h2_task_logio_add_bytes_out) {
-        /* count headers as if we'd do a HTTP/1.1 serialization */
-        task->output.written = h2_util_table_bytes(response->headers, 3)+1;
-        h2_task_logio_add_bytes_out(task->c, task->output.written);
-    }
-    ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03348)
-                  "h2_task(%s): open response to %s %s %s",
-                  task->id, task->request->method, 
-                  task->request->authority, 
-                  task->request->path);
-    return h2_mplx_out_open(task->mplx, task->stream_id, response);
-}
-
-static apr_status_t send_out(h2_task *task, apr_bucket_brigade* bb)
-{
-    apr_off_t written, left;
+    h2_task *task = h2_ctx_cget_task(filter->c);
     apr_status_t status;
-
-    apr_brigade_length(bb, 0, &written);
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
-                  "h2_task(%s): write response body (%ld bytes)", 
-                  task->id, (long)written);
     
-    status = h2_beam_send(task->output.beam, bb, 
-                          task->blocking? APR_BLOCK_READ
-                          : APR_NONBLOCK_READ);
-    if (APR_STATUS_IS_EAGAIN(status)) {
-        apr_brigade_length(bb, 0, &left);
-        written -= left;
-        status = APR_SUCCESS;
-    }
-    if (status == APR_SUCCESS) {
-        task->output.written += written;
-        if (h2_task_logio_add_bytes_out) {
-            h2_task_logio_add_bytes_out(task->c, written);
-        }
+    ap_assert(task);
+    status = slave_out(task, filter, brigade);
+    if (status != APR_SUCCESS) {
+        h2_task_rst(task, H2_ERR_INTERNAL_ERROR);
     }
     return status;
 }
 
-/* Bring the data from the brigade (which represents the result of the
- * request_rec out filter chain) into the h2_mplx for further sending
- * on the master connection. 
- */
-static apr_status_t output_write(h2_task *task, ap_filter_t* f, 
-                                 apr_bucket_brigade* bb)
+static apr_status_t h2_filter_parse_h1(ap_filter_t* f, apr_bucket_brigade* bb)
 {
-    apr_bucket *b;
-    apr_status_t status = APR_SUCCESS;
-    int flush = 0;
-    
-    if (APR_BRIGADE_EMPTY(bb)) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
-                      "h2_task(%s): empty write", task->id);
-        return APR_SUCCESS;
-    }
-    
-    if (task->frozen) {
-        h2_util_bb_log(task->c, task->stream_id, APLOG_TRACE2,
-                       "frozen task output write, ignored", bb);
-        while (!APR_BRIGADE_EMPTY(bb)) {
-            b = APR_BRIGADE_FIRST(bb);
-            if (AP_BUCKET_IS_EOR(b)) {
-                APR_BUCKET_REMOVE(b);
-                task->eor = b;
-            }
-            else {
-                apr_bucket_delete(b);
-            }
-        }
-        return APR_SUCCESS;
-    }
-    
-    if (!task->output.beam) {
-        h2_beam_create(&task->output.beam, task->pool, 
-                       task->stream_id, "output", 0); 
-    }
-    
-    /* Attempt to write saved brigade first */
-    if (task->output.bb && !APR_BRIGADE_EMPTY(task->output.bb)) {
-        status = send_out(task, task->output.bb); 
-        if (status != APR_SUCCESS) {
-            return status;
-        }
-    }
+    h2_task *task = h2_ctx_cget_task(f->c);
+    apr_status_t status;
     
-    /* If there is nothing saved (anymore), try to write the brigade passed */
-    if ((!task->output.bb || APR_BRIGADE_EMPTY(task->output.bb)) 
-        && !APR_BRIGADE_EMPTY(bb)) {
-        /* check if we have a flush before the end-of-request */
-        if (!task->output.response_open) {
-            for (b = APR_BRIGADE_FIRST(bb);
-                 b != APR_BRIGADE_SENTINEL(bb);
-                 b = APR_BUCKET_NEXT(b)) {
-                if (AP_BUCKET_IS_EOR(b)) {
-                    break;
-                }
-                else if (APR_BUCKET_IS_FLUSH(b)) {
-                    flush = 1;
-                }
-            }
-        }
-
-        status = send_out(task, bb); 
-        if (status != APR_SUCCESS) {
+    ap_assert(task);
+    /* There are cases where we need to parse a serialized http/1.1 
+     * response. One example is a 100-continue answer in serialized mode
+     * or via a mod_proxy setup */
+    while (bb && !task->output.sent_response) {
+        status = h2_from_h1_parse_response(task, f, bb);
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, f->c,
+                      "h2_task(%s): parsed response", task->id);
+        if (APR_BRIGADE_EMPTY(bb) || status != APR_SUCCESS) {
             return status;
         }
     }
     
-    /* If the passed brigade is not empty, save it before return */
-    if (!APR_BRIGADE_EMPTY(bb)) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, task->c, APLOGNO(03405)
-                      "h2_task(%s): could not write all, saving brigade", 
-                      task->id);
-        if (!task->output.bb) {
-            task->output.bb = apr_brigade_create(task->pool, 
-                                          task->c->bucket_alloc);
-        }
-        return ap_save_brigade(f, &task->output.bb, &bb, task->pool);
-    }
-    
-    if (!task->output.response_open 
-        && (flush || h2_beam_get_mem_used(task->output.beam) > (32*1024))) {
-        /* if we have enough buffered or we got a flush bucket, open
-        * the response now. */
-        status = open_response(task);
-        task->output.response_open = 1;
-    }
-    
-    return status;
-}
-
-static apr_status_t output_finish(h2_task *task)
-{
-    apr_status_t status = APR_SUCCESS;
-    
-    if (!task->output.response_open) {
-        status = open_response(task);
-        task->output.response_open = 1;
-    }
-    return status;
-}
-
-/*******************************************************************************
- * task slave connection filters
- ******************************************************************************/
-
-static apr_status_t h2_filter_stream_input(ap_filter_t* filter,
-                                           apr_bucket_brigade* brigade,
-                                           ap_input_mode_t mode,
-                                           apr_read_type_e block,
-                                           apr_off_t readbytes)
-{
-    h2_task *task = h2_ctx_cget_task(filter->c);
-    AP_DEBUG_ASSERT(task);
-    return input_read(task, filter, brigade, mode, block, readbytes);
-}
-
-static apr_status_t h2_filter_stream_output(ap_filter_t* filter,
-                                            apr_bucket_brigade* brigade)
-{
-    h2_task *task = h2_ctx_cget_task(filter->c);
-    AP_DEBUG_ASSERT(task);
-    return output_write(task, filter, brigade);
-}
-
-static apr_status_t h2_filter_read_response(ap_filter_t* filter,
-                                            apr_bucket_brigade* bb)
-{
-    h2_task *task = h2_ctx_cget_task(filter->c);
-    AP_DEBUG_ASSERT(task);
-    if (!task->output.from_h1) {
-        return APR_ECONNABORTED;
-    }
-    return h2_from_h1_read_response(task->output.from_h1, filter, bb);
+    return ap_pass_brigade(f->next, bb);
 }
 
 /*******************************************************************************
  * task things
  ******************************************************************************/
  
-void h2_task_set_response(h2_task *task, h2_response *response) 
-{
-    AP_DEBUG_ASSERT(response);
-    AP_DEBUG_ASSERT(!task->response);
-    /* we used to clone the response into out own pool. But
-     * we have much tighter control over the EOR bucket nowadays,
-     * so just use the instance given */
-    task->response = response;
-    if (response->rst_error) {
-        h2_task_rst(task, response->rst_error);
-    }
-}
-
-
 int h2_task_can_redo(h2_task *task) {
-    if (task->submitted
-        || (task->input.beam && h2_beam_was_received(task->input.beam)) 
-        || !task->request) {
+    if (task->input.beam && h2_beam_was_received(task->input.beam)) {
         /* cannot repeat that. */
         return 0;
     }
@@ -540,7 +426,6 @@ int h2_task_can_redo(h2_task *task) {
 
 void h2_task_redo(h2_task *task)
 {
-    task->response = NULL;
     task->rst_error = 0;
 }
 
@@ -548,9 +433,9 @@ void h2_task_rst(h2_task *task, int erro
 {
     task->rst_error = error;
     if (task->input.beam) {
-        h2_beam_abort(task->input.beam);
+        h2_beam_leave(task->input.beam);
     }
-    if (task->output.beam) {
+    if (!task->worker_done) {
         h2_beam_abort(task->output.beam);
     }
     if (task->c) {
@@ -582,15 +467,18 @@ void h2_task_register_hooks(void)
     ap_hook_process_connection(h2_task_process_conn, 
                                NULL, NULL, APR_HOOK_FIRST);
 
-    ap_register_output_filter("H2_RESPONSE", h2_response_output_filter,
-                              NULL, AP_FTYPE_PROTOCOL);
-    ap_register_input_filter("H2_TO_H1", h2_filter_stream_input,
+    ap_register_input_filter("H2_SLAVE_IN", h2_filter_slave_in,
                              NULL, AP_FTYPE_NETWORK);
-    ap_register_output_filter("H1_TO_H2", h2_filter_stream_output,
+    ap_register_output_filter("H2_SLAVE_OUT", h2_filter_slave_output,
+                              NULL, AP_FTYPE_NETWORK);
+    ap_register_output_filter("H2_PARSE_H1", h2_filter_parse_h1,
                               NULL, AP_FTYPE_NETWORK);
-    ap_register_output_filter("H1_TO_H2_RESP", h2_filter_read_response,
+
+    ap_register_input_filter("H2_REQUEST", h2_filter_request_in,
+                             NULL, AP_FTYPE_PROTOCOL);
+    ap_register_output_filter("H2_RESPONSE", h2_filter_headers_out,
                               NULL, AP_FTYPE_PROTOCOL);
-    ap_register_output_filter("H2_TRAILERS", h2_response_trailers_filter,
+    ap_register_output_filter("H2_TRAILERS_OUT", h2_filter_trailers_out,
                               NULL, AP_FTYPE_PROTOCOL);
 }
 
@@ -616,53 +504,51 @@ static int h2_task_pre_conn(conn_rec* c,
     if (h2_ctx_is_task(ctx)) {
         ap_log_cerror(APLOG_MARK, APLOG_TRACE2, 0, c,
                       "h2_h2, pre_connection, found stream task");
-        
-        /* Add our own, network level in- and output filters.
-         */
-        ap_add_input_filter("H2_TO_H1", NULL, NULL, c);
-        ap_add_output_filter("H1_TO_H2", NULL, NULL, c);
+        ap_add_input_filter("H2_SLAVE_IN", NULL, NULL, c);
+        ap_add_output_filter("H2_PARSE_H1", NULL, NULL, c);
+        ap_add_output_filter("H2_SLAVE_OUT", NULL, NULL, c);
     }
     return OK;
 }
 
-h2_task *h2_task_create(conn_rec *c, const h2_request *req, 
-                        h2_bucket_beam *input, h2_mplx *mplx)
+h2_task *h2_task_create(conn_rec *slave, int stream_id,
+                        const h2_request *req, h2_mplx *m,
+                        h2_bucket_beam *input, 
+                        apr_interval_time_t timeout,
+                        apr_size_t output_max_mem)
 {
     apr_pool_t *pool;
     h2_task *task;
     
-    apr_pool_create(&pool, c->pool);
+    ap_assert(slave);
+    ap_assert(req);
+
+    apr_pool_create(&pool, slave->pool);
     task = apr_pcalloc(pool, sizeof(h2_task));
     if (task == NULL) {
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, APR_ENOMEM, c,
-                      APLOGNO(02941) "h2_task(%ld-%d): create stream task", 
-                      c->id, req->id);
         return NULL;
     }
-    
-    task->id          = apr_psprintf(pool, "%ld-%d", c->id, req->id);
-    task->stream_id   = req->id;
-    task->c           = c;
-    task->mplx        = mplx;
-    task->c->keepalives = mplx->c->keepalives;
+    task->id          = "000";
+    task->stream_id   = stream_id;
+    task->c           = slave;
+    task->mplx        = m;
     task->pool        = pool;
     task->request     = req;
-    task->ser_headers = req->serialize;
-    task->blocking    = 1;
+    task->timeout     = timeout;
     task->input.beam  = input;
-    
-    apr_thread_cond_create(&task->cond, pool);
+    task->output.max_buffer = output_max_mem;
 
-    h2_ctx_create_for(c, task);
     return task;
 }
 
 void h2_task_destroy(h2_task *task)
 {
     if (task->output.beam) {
+        h2_beam_log(task->output.beam, task->c, APLOG_TRACE2, "task_destroy");
         h2_beam_destroy(task->output.beam);
         task->output.beam = NULL;
     }
+    
     if (task->eor) {
         apr_bucket_destroy(task->eor);
     }
@@ -671,57 +557,91 @@ void h2_task_destroy(h2_task *task)
     }
 }
 
-void h2_task_set_io_blocking(h2_task *task, int blocking)
-{
-    task->blocking = blocking;
-}
-
-apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread)
+apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread, int worker_id)
 {
-    AP_DEBUG_ASSERT(task);
+    conn_rec *c;
     
-    task->input.block = APR_BLOCK_READ;
-    task->input.chunked = task->request->chunked;
-    task->input.eos = !task->request->body;
-    if (task->input.eos && !task->input.chunked && !task->ser_headers) {
-        /* We do not serialize/chunk and have eos already, no need to
-         * create a bucket brigade. */
-        task->input.bb = NULL;
-        task->input.eos_written = 1;
-    }
-    else {
-        task->input.bb = apr_brigade_create(task->pool, task->c->bucket_alloc);
-        if (task->ser_headers) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
-                          "h2_task(%s): serialize request %s %s", 
-                          task->id, task->request->method, task->request->path);
-            apr_brigade_printf(task->input.bb, NULL, 
-                               NULL, "%s %s HTTP/1.1\r\n", 
-                               task->request->method, task->request->path);
-            apr_table_do(input_ser_header, task, task->request->headers, NULL);
-            apr_brigade_puts(task->input.bb, NULL, NULL, "\r\n");
+    ap_assert(task);
+    c = task->c;
+    task->worker_started = 1;
+    task->started_at = apr_time_now();
+    
+    if (c->master) {
+        /* Each conn_rec->id is supposed to be unique at a point in time. Since
+         * some modules (and maybe external code) uses this id as an identifier
+         * for the request_rec they handle, it needs to be unique for slave 
+         * connections also.
+         * The connection id is generated by the MPM and most MPMs use the formula
+         *    id := (child_num * max_threads) + thread_num
+         * which means that there is a maximum id of about
+         *    idmax := max_child_count * max_threads
+         * If we assume 2024 child processes with 2048 threads max, we get
+         *    idmax ~= 2024 * 2048 = 2 ** 22
+         * On 32 bit systems, we have not much space left, but on 64 bit systems
+         * (and higher?) we can use the upper 32 bits without fear of collision.
+         * 32 bits is just what we need, since a connection can only handle so
+         * many streams. 
+         */
+        int slave_id, free_bits;
+        
+        task->id = apr_psprintf(task->pool, "%ld-%d", c->master->id, 
+                                task->stream_id);
+        if (sizeof(unsigned long) >= 8) {
+            free_bits = 32;
+            slave_id = task->stream_id;
         }
-        if (task->input.eos) {
-            input_append_eos(task, NULL);
+        else {
+            /* Assume we have a more limited number of threads/processes
+             * and h2 workers on a 32-bit system. Use the worker instead
+             * of the stream id. */
+            free_bits = 8;
+            slave_id = worker_id; 
         }
+        task->c->id = (c->master->id << free_bits)^slave_id;
+        c->keepalive = AP_CONN_KEEPALIVE;
+    }
+        
+    h2_beam_create(&task->output.beam, c->pool, task->stream_id, "output", 
+                   H2_BEAM_OWNER_SEND, 0, task->timeout);
+    if (!task->output.beam) {
+        return APR_ENOMEM;
     }
     
-    task->output.from_h1 = h2_from_h1_create(task->stream_id, task->pool);
+    h2_beam_buffer_size_set(task->output.beam, task->output.max_buffer);
+    h2_beam_send_from(task->output.beam, task->pool);
     
-    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
+    h2_ctx_create_for(c, task);
+    apr_table_setn(c->notes, H2_TASK_ID_NOTE, task->id);
+
+    h2_slave_run_pre_connection(c, ap_get_conn_socket(c));            
+
+    task->input.bb = apr_brigade_create(task->pool, c->bucket_alloc);
+    if (task->request->serialize) {
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                      "h2_task(%s): serialize request %s %s", 
+                      task->id, task->request->method, task->request->path);
+        apr_brigade_printf(task->input.bb, NULL, 
+                           NULL, "%s %s HTTP/1.1\r\n", 
+                           task->request->method, task->request->path);
+        apr_table_do(input_ser_header, task, task->request->headers, NULL);
+        apr_brigade_puts(task->input.bb, NULL, NULL, "\r\n");
+    }
+    
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                   "h2_task(%s): process connection", task->id);
+                  
     task->c->current_thread = thread; 
-    ap_run_process_connection(task->c);
+    ap_run_process_connection(c);
     
     if (task->frozen) {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                       "h2_task(%s): process_conn returned frozen task", 
                       task->id);
         /* cleanup delayed */
         return APR_EAGAIN;
     }
     else {
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, task->c,
+        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                       "h2_task(%s): processing done", task->id);
         return output_finish(task);
     }
@@ -737,6 +657,15 @@ static apr_status_t h2_task_process_requ
                   "h2_task(%s): create request_rec", task->id);
     r = h2_request_create_rec(req, c);
     if (r && (r->status == HTTP_OK)) {
+        /* set timeouts for virtual host of request */
+        if (task->timeout != r->server->timeout) {
+            task->timeout = r->server->timeout;
+            h2_beam_timeout_set(task->output.beam, task->timeout);
+            if (task->input.beam) {
+                h2_beam_timeout_set(task->input.beam, task->timeout);
+            }
+        }
+        
         ap_update_child_status(c->sbh, SERVER_BUSY_WRITE, r);
         
         if (cs) {
@@ -744,16 +673,19 @@ static apr_status_t h2_task_process_requ
         }
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                       "h2_task(%s): start process_request", task->id);
+    
         ap_process_request(r);
         if (task->frozen) {
             ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
                           "h2_task(%s): process_request frozen", task->id);
         }
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
-                      "h2_task(%s): process_request done", task->id);
+        else {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c,
+                          "h2_task(%s): process_request done", task->id);
+        }
         
         /* After the call to ap_process_request, the
-         * request pool will have been deleted.  We set
+         * request pool may have been deleted.  We set
          * r=NULL here to ensure that any dereference
          * of r that might be added later in this function
          * will result in a segfault immediately instead
@@ -786,7 +718,7 @@ static int h2_task_process_conn(conn_rec
     
     ctx = h2_ctx_get(c, 0);
     if (h2_ctx_is_task(ctx)) {
-        if (!ctx->task->ser_headers) {
+        if (!ctx->task->request->serialize) {
             ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, 
                           "h2_h2, processing request directly");
             h2_task_process_request(ctx->task, c);
@@ -819,11 +751,11 @@ apr_status_t h2_task_thaw(h2_task *task)
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, task->c, APLOGNO(03407) 
                       "h2_task(%s), thawed", task->id);
     }
-    task->detached = 1;
+    task->thawed = 1;
     return APR_SUCCESS;
 }
 
-int h2_task_is_detached(h2_task *task)
+int h2_task_has_thawed(h2_task *task)
 {
-    return task->detached;
+    return task->thawed;
 }
diff -up --new-file httpd-2.4.23/modules/http2/h2_task.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_task.h
--- httpd-2.4.23/modules/http2/h2_task.h	2016-06-07 13:29:51.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_task.h	2017-03-31 21:41:01.000000000 +0200
@@ -37,14 +37,14 @@
  * of our own to disble those.
  */
 
-struct apr_thread_cond_t;
 struct h2_bucket_beam;
 struct h2_conn;
 struct h2_mplx;
 struct h2_task;
 struct h2_req_engine;
 struct h2_request;
-struct h2_response;
+struct h2_response_parser;
+struct h2_stream;
 struct h2_worker;
 
 typedef struct h2_task h2_task;
@@ -56,37 +56,33 @@ struct h2_task {
     apr_pool_t *pool;
     
     const struct h2_request *request;
-    struct h2_response *response;
+    apr_interval_time_t timeout;
+    int rst_error;                   /* h2 related stream abort error */
     
     struct {
         struct h2_bucket_beam *beam;
-        apr_bucket_brigade *bb;
-        apr_bucket_brigade *tmp;
-        apr_read_type_e block;
-        unsigned int chunked : 1;
         unsigned int eos : 1;
-        unsigned int eos_written : 1;
+        apr_bucket_brigade *bb;
+        apr_bucket_brigade *bbchunk;
+        apr_off_t chunked_total;
     } input;
     struct {
         struct h2_bucket_beam *beam;
-        struct h2_from_h1 *from_h1;
-        unsigned int response_open : 1;
-        apr_off_t written;
+        unsigned int opened : 1;
+        unsigned int sent_response : 1;
+        unsigned int copy_files : 1;
+        struct h2_response_parser *rparser;
         apr_bucket_brigade *bb;
+        apr_size_t max_buffer;
     } output;
     
     struct h2_mplx *mplx;
-    struct apr_thread_cond_t *cond;
     
-    int rst_error;                   /* h2 related stream abort error */
     unsigned int filters_set    : 1;
-    unsigned int ser_headers    : 1;
     unsigned int frozen         : 1;
-    unsigned int blocking       : 1;
-    unsigned int detached       : 1;
-    unsigned int submitted      : 1; /* response has been submitted to client */
-    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 thawed         : 1;
+    unsigned int worker_started : 1; /* h2_worker started processing */
+    unsigned int worker_done    : 1; /* h2_worker finished */
     
     apr_time_t started_at;           /* when processing started */
     apr_time_t done_at;              /* when processing was done */
@@ -94,17 +90,17 @@ struct h2_task {
     
     struct h2_req_engine *engine;   /* engine hosted by this task */
     struct h2_req_engine *assigned; /* engine that task has been assigned to */
-    request_rec *r;                 /* request being processed in this task */
 };
 
-h2_task *h2_task_create(conn_rec *c, const struct h2_request *req, 
-                        struct h2_bucket_beam *input, struct h2_mplx *mplx);
+h2_task *h2_task_create(conn_rec *slave, int stream_id,
+                        const h2_request *req, struct h2_mplx *m, 
+                        struct h2_bucket_beam *input, 
+                        apr_interval_time_t timeout,
+                        apr_size_t output_max_mem);
 
 void h2_task_destroy(h2_task *task);
 
-apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread);
-
-void h2_task_set_response(h2_task *task, struct h2_response *response);
+apr_status_t h2_task_do(h2_task *task, apr_thread_t *thread, int worker_id);
 
 void h2_task_redo(h2_task *task);
 int h2_task_can_redo(h2_task *task);
@@ -125,8 +121,6 @@ extern APR_OPTIONAL_FN_TYPE(ap_logio_add
 
 apr_status_t h2_task_freeze(h2_task *task);
 apr_status_t h2_task_thaw(h2_task *task);
-int h2_task_is_detached(h2_task *task);
-
-void h2_task_set_io_blocking(h2_task *task, int blocking);
+int h2_task_has_thawed(h2_task *task);
 
 #endif /* defined(__mod_h2__h2_task__) */
diff -up --new-file httpd-2.4.23/modules/http2/h2_util.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_util.c
--- httpd-2.4.23/modules/http2/h2_util.c	2016-06-14 10:51:31.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_util.c	2017-10-13 10:37:45.000000000 +0200
@@ -15,6 +15,8 @@
 
 #include <assert.h>
 #include <apr_strings.h>
+#include <apr_thread_mutex.h>
+#include <apr_thread_cond.h>
 
 #include <httpd.h>
 #include <http_core.h>
@@ -27,7 +29,7 @@
 #include "h2_util.h"
 
 /* h2_log2(n) iff n is a power of 2 */
-unsigned char h2_log2(apr_uint32_t n)
+unsigned char h2_log2(int n)
 {
     int lz = 0;
     if (!n) {
@@ -110,6 +112,8 @@ void h2_util_camel_case_header(char *s,
     }
 }
 
+/* base64 url encoding ****************************************************************************/
+
 static const int BASE64URL_UINT6[] = {
 /*   0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f        */
     -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, /*  0 */
@@ -172,6 +176,7 @@ apr_size_t h2_util_base64url_decode(cons
             n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) +
                  (BASE64URL_UINT6[ e[mlen+1] ] << 12));
             *d++ = n >> 16;
+            remain = 1;
             break;
         case 3:
             n = ((BASE64URL_UINT6[ e[mlen+0] ] << 18) +
@@ -179,6 +184,7 @@ apr_size_t h2_util_base64url_decode(cons
                  (BASE64URL_UINT6[ e[mlen+2] ] << 6));
             *d++ = n >> 16;
             *d++ = n >> 8 & 0xffu;
+            remain = 2;
             break;
         default: /* do nothing */
             break;
@@ -187,78 +193,35 @@ apr_size_t h2_util_base64url_decode(cons
 }
 
 const char *h2_util_base64url_encode(const char *data, 
-                                     apr_size_t len, apr_pool_t *pool)
+                                     apr_size_t dlen, apr_pool_t *pool)
 {
-    apr_size_t mlen = ((len+2)/3)*3;
-    apr_size_t slen = (mlen/3)*4;
-    apr_size_t i;
+    long i, len = (int)dlen;
+    apr_size_t slen = ((dlen+2)/3)*4 + 1; /* 0 terminated */
     const unsigned char *udata = (const unsigned char*)data;
-    char *enc, *p = apr_pcalloc(pool, slen+1); /* 0 terminated */
+    char *enc, *p = apr_pcalloc(pool, slen);
     
     enc = p;
-    for (i = 0; i < mlen; i+= 3) {
+    for (i = 0; i < len-2; i+= 3) {
         *p++ = BASE64URL_CHARS[ (udata[i] >> 2) & 0x3fu ];
-        *p++ = BASE64URL_CHARS[ ((udata[i] << 4) + 
-                                 ((i+1 < len)? (udata[i+1] >> 4) : 0)) & 0x3fu ];
-        *p++ = BASE64URL_CHARS[ ((udata[i+1] << 2) + 
-                                 ((i+2 < len)? (udata[i+2] >> 6) : 0)) & 0x3fu ];
-        if (i+2 < len) {
-            *p++ = BASE64URL_CHARS[ udata[i+2] & 0x3fu ];
-        }
+        *p++ = BASE64URL_CHARS[ ((udata[i] << 4) + (udata[i+1] >> 4)) & 0x3fu ];
+        *p++ = BASE64URL_CHARS[ ((udata[i+1] << 2) + (udata[i+2] >> 6)) & 0x3fu ];
+        *p++ = BASE64URL_CHARS[ udata[i+2] & 0x3fu ];
     }
     
-    return enc;
-}
-
-int h2_util_contains_token(apr_pool_t *pool, const char *s, const char *token)
-{
-    char *c;
-    if (s) {
-        if (!apr_strnatcasecmp(s, token)) {   /* the simple life */
-            return 1;
-        }
-        
-        for (c = ap_get_token(pool, &s, 0); c && *c;
-             c = *s? ap_get_token(pool, &s, 0) : NULL) {
-            if (!apr_strnatcasecmp(c, token)) { /* seeing the token? */
-                return 1;
-            }
-            while (*s++ == ';') {            /* skip parameters */
-                ap_get_token(pool, &s, 0);
-            }
-            if (*s++ != ',') {               /* need comma separation */
-                return 0;
-            }
+    if (i < len) {
+        *p++ = BASE64URL_CHARS[ (udata[i] >> 2) & 0x3fu ];
+        if (i == (len - 1)) {
+            *p++ = BASE64URL_CHARS[ (udata[i] << 4) & 0x3fu ];
         }
-    }
-    return 0;
-}
-
-const char *h2_util_first_token_match(apr_pool_t *pool, const char *s, 
-                                      const char *tokens[], apr_size_t len)
-{
-    char *c;
-    apr_size_t i;
-    if (s && *s) {
-        for (c = ap_get_token(pool, &s, 0); c && *c;
-             c = *s? ap_get_token(pool, &s, 0) : NULL) {
-            for (i = 0; i < len; ++i) {
-                if (!apr_strnatcasecmp(c, tokens[i])) {
-                    return tokens[i];
-                }
-            }
-            while (*s++ == ';') {            /* skip parameters */
-                ap_get_token(pool, &s, 0);
-            }
-            if (*s++ != ',') {               /* need comma separation */
-                return 0;
-            }
+        else {
+            *p++ = BASE64URL_CHARS[ ((udata[i] << 4) + (udata[i+1] >> 4)) & 0x3fu ];
+            *p++ = BASE64URL_CHARS[ (udata[i+1] << 2) & 0x3fu ];
         }
     }
-    return NULL;
+    *p++ = '\0';
+    return enc;
 }
 
-
 /*******************************************************************************
  * ihash - hash for structs with int identifier
  ******************************************************************************/
@@ -370,39 +333,6 @@ size_t h2_ihash_shift(h2_ihash_t *ih, vo
     return ctx.len;
 }
 
-typedef struct {
-    h2_ihash_t *ih;
-    int *buffer;
-    size_t max;
-    size_t len;
-} icollect_ctx;
-
-static int icollect_iter(void *x, void *val)
-{
-    icollect_ctx *ctx = x;
-    if (ctx->len < ctx->max) {
-        ctx->buffer[ctx->len++] = *((int*)((char *)val + ctx->ih->ioff));
-        return 1;
-    }
-    return 0;
-}
-
-size_t h2_ihash_ishift(h2_ihash_t *ih, int *buffer, size_t max)
-{
-    icollect_ctx ctx;
-    size_t i;
-    
-    ctx.ih = ih;
-    ctx.buffer = buffer;
-    ctx.max = max;
-    ctx.len = 0;
-    h2_ihash_iter(ih, icollect_iter, &ctx);
-    for (i = 0; i < ctx.len; ++i) {
-        h2_ihash_remove(ih, buffer[i]);
-    }
-    return ctx.len;
-}
-
 /*******************************************************************************
  * iqueue - sorted list of int
  ******************************************************************************/
@@ -436,14 +366,16 @@ int h2_iq_count(h2_iqueue *q)
 }
 
 
-void h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx)
+int h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx)
 {
     int i;
     
+    if (h2_iq_contains(q, sid)) {
+        return 0;
+    }
     if (q->nelts >= q->nalloc) {
         iq_grow(q, q->nalloc * 2);
     }
-    
     i = (q->head + q->nelts) % q->nalloc;
     q->elts[i] = sid;
     ++q->nelts;
@@ -452,6 +384,12 @@ void h2_iq_add(h2_iqueue *q, int sid, h2
         /* bubble it to the front of the queue */
         iq_bubble_up(q, i, q->head, cmp, ctx);
     }
+    return 1;
+}
+
+int h2_iq_append(h2_iqueue *q, int sid)
+{
+    return h2_iq_add(q, sid, NULL, NULL);
 }
 
 int h2_iq_remove(h2_iqueue *q, int sid)
@@ -522,6 +460,18 @@ int h2_iq_shift(h2_iqueue *q)
     return sid;
 }
 
+size_t h2_iq_mshift(h2_iqueue *q, int *pint, size_t max)
+{
+    int i;
+    for (i = 0; i < max; ++i) {
+        pint[i] = h2_iq_shift(q);
+        if (pint[i] == 0) {
+            break;
+        }
+    }
+    return i;
+}
+
 static void iq_grow(h2_iqueue *q, int nlen)
 {
     if (nlen > q->nalloc) {
@@ -573,6 +523,633 @@ static int iq_bubble_down(h2_iqueue *q,
     return i;
 }
 
+int h2_iq_contains(h2_iqueue *q, int sid)
+{
+    int i;
+    for (i = 0; i < q->nelts; ++i) {
+        if (sid == q->elts[(q->head + i) % q->nalloc]) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+/*******************************************************************************
+ * FIFO queue
+ ******************************************************************************/
+
+struct h2_fifo {
+    void **elems;
+    int nelems;
+    int set;
+    int head;
+    int count;
+    int aborted;
+    apr_thread_mutex_t *lock;
+    apr_thread_cond_t  *not_empty;
+    apr_thread_cond_t  *not_full;
+};
+
+static int nth_index(h2_fifo *fifo, int n) 
+{
+    return (fifo->head + n) % fifo->nelems;
+}
+
+static apr_status_t fifo_destroy(void *data) 
+{
+    h2_fifo *fifo = data;
+
+    apr_thread_cond_destroy(fifo->not_empty);
+    apr_thread_cond_destroy(fifo->not_full);
+    apr_thread_mutex_destroy(fifo->lock);
+
+    return APR_SUCCESS;
+}
+
+static int index_of(h2_fifo *fifo, void *elem)
+{
+    int i;
+    
+    for (i = 0; i < fifo->count; ++i) {
+        if (elem == fifo->elems[nth_index(fifo, i)]) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+static apr_status_t create_int(h2_fifo **pfifo, apr_pool_t *pool, 
+                               int capacity, int as_set)
+{
+    apr_status_t rv;
+    h2_fifo *fifo;
+    
+    fifo = apr_pcalloc(pool, sizeof(*fifo));
+    if (fifo == NULL) {
+        return APR_ENOMEM;
+    }
+
+    rv = apr_thread_mutex_create(&fifo->lock,
+                                 APR_THREAD_MUTEX_UNNESTED, pool);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    rv = apr_thread_cond_create(&fifo->not_empty, pool);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    rv = apr_thread_cond_create(&fifo->not_full, pool);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    fifo->elems = apr_pcalloc(pool, capacity * sizeof(void*));
+    if (fifo->elems == NULL) {
+        return APR_ENOMEM;
+    }
+    fifo->nelems = capacity;
+    fifo->set = as_set;
+    
+    *pfifo = fifo;
+    apr_pool_cleanup_register(pool, fifo, fifo_destroy, apr_pool_cleanup_null);
+
+    return APR_SUCCESS;
+}
+
+apr_status_t h2_fifo_create(h2_fifo **pfifo, apr_pool_t *pool, int capacity)
+{
+    return create_int(pfifo, pool, capacity, 0);
+}
+
+apr_status_t h2_fifo_set_create(h2_fifo **pfifo, apr_pool_t *pool, int capacity)
+{
+    return create_int(pfifo, pool, capacity, 1);
+}
+
+apr_status_t h2_fifo_term(h2_fifo *fifo)
+{
+    apr_status_t rv;
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        fifo->aborted = 1;
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+apr_status_t h2_fifo_interrupt(h2_fifo *fifo)
+{
+    apr_status_t rv;
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        apr_thread_cond_broadcast(fifo->not_empty);
+        apr_thread_cond_broadcast(fifo->not_full);
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+int h2_fifo_count(h2_fifo *fifo)
+{
+    return fifo->count;
+}
+
+static apr_status_t check_not_empty(h2_fifo *fifo, int block)
+{
+    while (fifo->count == 0) {
+        if (!block) {
+            return APR_EAGAIN;
+        }
+        if (fifo->aborted) {
+            return APR_EOF;
+        }
+        apr_thread_cond_wait(fifo->not_empty, fifo->lock);
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t fifo_push_int(h2_fifo *fifo, void *elem, int block)
+{
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+
+    if (fifo->set && index_of(fifo, elem) >= 0) {
+        /* set mode, elem already member */
+        return APR_EEXIST;
+    }
+    else if (fifo->count == fifo->nelems) {
+        if (block) {
+            while (fifo->count == fifo->nelems) {
+                if (fifo->aborted) {
+                    return APR_EOF;
+                }
+                apr_thread_cond_wait(fifo->not_full, fifo->lock);
+            }
+        }
+        else {
+            return APR_EAGAIN;
+        }
+    }
+    
+    ap_assert(fifo->count < fifo->nelems);
+    fifo->elems[nth_index(fifo, fifo->count)] = elem;
+    ++fifo->count;
+    if (fifo->count == 1) {
+        apr_thread_cond_broadcast(fifo->not_empty);
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t fifo_push(h2_fifo *fifo, void *elem, int block)
+{
+    apr_status_t rv;
+    
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        rv = fifo_push_int(fifo, elem, block);
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+apr_status_t h2_fifo_push(h2_fifo *fifo, void *elem)
+{
+    return fifo_push(fifo, elem, 1);
+}
+
+apr_status_t h2_fifo_try_push(h2_fifo *fifo, void *elem)
+{
+    return fifo_push(fifo, elem, 0);
+}
+
+static apr_status_t pull_head(h2_fifo *fifo, void **pelem, int block)
+{
+    apr_status_t rv;
+    
+    if ((rv = check_not_empty(fifo, block)) != APR_SUCCESS) {
+        *pelem = NULL;
+        return rv;
+    }
+    *pelem = fifo->elems[fifo->head];
+    --fifo->count;
+    if (fifo->count > 0) {
+        fifo->head = nth_index(fifo, 1);
+        if (fifo->count+1 == fifo->nelems) {
+            apr_thread_cond_broadcast(fifo->not_full);
+        }
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t fifo_pull(h2_fifo *fifo, void **pelem, int block)
+{
+    apr_status_t rv;
+    
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+    
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        rv = pull_head(fifo, pelem, block);
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+apr_status_t h2_fifo_pull(h2_fifo *fifo, void **pelem)
+{
+    return fifo_pull(fifo, pelem, 1);
+}
+
+apr_status_t h2_fifo_try_pull(h2_fifo *fifo, void **pelem)
+{
+    return fifo_pull(fifo, pelem, 0);
+}
+
+static apr_status_t fifo_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx, int block)
+{
+    apr_status_t rv;
+    void *elem;
+    
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+    
+    if (APR_SUCCESS == (rv = apr_thread_mutex_lock(fifo->lock))) {
+        if (APR_SUCCESS == (rv = pull_head(fifo, &elem, block))) {
+            switch (fn(elem, ctx)) {
+                case H2_FIFO_OP_PULL:
+                    break;
+                case H2_FIFO_OP_REPUSH:
+                    rv = fifo_push_int(fifo, elem, block);
+                    break;
+            }
+        }
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+apr_status_t h2_fifo_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx)
+{
+    return fifo_peek(fifo, fn, ctx, 1);
+}
+
+apr_status_t h2_fifo_try_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx)
+{
+    return fifo_peek(fifo, fn, ctx, 0);
+}
+
+apr_status_t h2_fifo_remove(h2_fifo *fifo, void *elem)
+{
+    apr_status_t rv;
+    
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        int i, rc;
+        void *e;
+        
+        rc = 0;
+        for (i = 0; i < fifo->count; ++i) {
+            e = fifo->elems[nth_index(fifo, i)];
+            if (e == elem) {
+                ++rc;
+            }
+            else if (rc) {
+                fifo->elems[nth_index(fifo, i-rc)] = e;
+            }
+        }
+        if (rc) {
+            fifo->count -= rc;
+            if (fifo->count + rc == fifo->nelems) {
+                apr_thread_cond_broadcast(fifo->not_full);
+            }
+            rv = APR_SUCCESS;
+        }
+        else {
+            rv = APR_EAGAIN;
+        }
+        
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+/*******************************************************************************
+ * FIFO int queue
+ ******************************************************************************/
+
+struct h2_ififo {
+    int *elems;
+    int nelems;
+    int set;
+    int head;
+    int count;
+    int aborted;
+    apr_thread_mutex_t *lock;
+    apr_thread_cond_t  *not_empty;
+    apr_thread_cond_t  *not_full;
+};
+
+static int inth_index(h2_ififo *fifo, int n) 
+{
+    return (fifo->head + n) % fifo->nelems;
+}
+
+static apr_status_t ififo_destroy(void *data) 
+{
+    h2_ififo *fifo = data;
+
+    apr_thread_cond_destroy(fifo->not_empty);
+    apr_thread_cond_destroy(fifo->not_full);
+    apr_thread_mutex_destroy(fifo->lock);
+
+    return APR_SUCCESS;
+}
+
+static int iindex_of(h2_ififo *fifo, int id)
+{
+    int i;
+    
+    for (i = 0; i < fifo->count; ++i) {
+        if (id == fifo->elems[inth_index(fifo, i)]) {
+            return i;
+        }
+    }
+    return -1;
+}
+
+static apr_status_t icreate_int(h2_ififo **pfifo, apr_pool_t *pool, 
+                                int capacity, int as_set)
+{
+    apr_status_t rv;
+    h2_ififo *fifo;
+    
+    fifo = apr_pcalloc(pool, sizeof(*fifo));
+    if (fifo == NULL) {
+        return APR_ENOMEM;
+    }
+
+    rv = apr_thread_mutex_create(&fifo->lock,
+                                 APR_THREAD_MUTEX_UNNESTED, pool);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    rv = apr_thread_cond_create(&fifo->not_empty, pool);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    rv = apr_thread_cond_create(&fifo->not_full, pool);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    fifo->elems = apr_pcalloc(pool, capacity * sizeof(int));
+    if (fifo->elems == NULL) {
+        return APR_ENOMEM;
+    }
+    fifo->nelems = capacity;
+    fifo->set = as_set;
+    
+    *pfifo = fifo;
+    apr_pool_cleanup_register(pool, fifo, ififo_destroy, apr_pool_cleanup_null);
+
+    return APR_SUCCESS;
+}
+
+apr_status_t h2_ififo_create(h2_ififo **pfifo, apr_pool_t *pool, int capacity)
+{
+    return icreate_int(pfifo, pool, capacity, 0);
+}
+
+apr_status_t h2_ififo_set_create(h2_ififo **pfifo, apr_pool_t *pool, int capacity)
+{
+    return icreate_int(pfifo, pool, capacity, 1);
+}
+
+apr_status_t h2_ififo_term(h2_ififo *fifo)
+{
+    apr_status_t rv;
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        fifo->aborted = 1;
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+apr_status_t h2_ififo_interrupt(h2_ififo *fifo)
+{
+    apr_status_t rv;
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        apr_thread_cond_broadcast(fifo->not_empty);
+        apr_thread_cond_broadcast(fifo->not_full);
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+int h2_ififo_count(h2_ififo *fifo)
+{
+    return fifo->count;
+}
+
+static apr_status_t icheck_not_empty(h2_ififo *fifo, int block)
+{
+    while (fifo->count == 0) {
+        if (!block) {
+            return APR_EAGAIN;
+        }
+        if (fifo->aborted) {
+            return APR_EOF;
+        }
+        apr_thread_cond_wait(fifo->not_empty, fifo->lock);
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t ififo_push_int(h2_ififo *fifo, int id, int block)
+{
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+
+    if (fifo->set && iindex_of(fifo, id) >= 0) {
+        /* set mode, elem already member */
+        return APR_EEXIST;
+    }
+    else if (fifo->count == fifo->nelems) {
+        if (block) {
+            while (fifo->count == fifo->nelems) {
+                if (fifo->aborted) {
+                    return APR_EOF;
+                }
+                apr_thread_cond_wait(fifo->not_full, fifo->lock);
+            }
+        }
+        else {
+            return APR_EAGAIN;
+        }
+    }
+    
+    ap_assert(fifo->count < fifo->nelems);
+    fifo->elems[inth_index(fifo, fifo->count)] = id;
+    ++fifo->count;
+    if (fifo->count == 1) {
+        apr_thread_cond_broadcast(fifo->not_empty);
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t ififo_push(h2_ififo *fifo, int id, int block)
+{
+    apr_status_t rv;
+    
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        rv = ififo_push_int(fifo, id, block);
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+apr_status_t h2_ififo_push(h2_ififo *fifo, int id)
+{
+    return ififo_push(fifo, id, 1);
+}
+
+apr_status_t h2_ififo_try_push(h2_ififo *fifo, int id)
+{
+    return ififo_push(fifo, id, 0);
+}
+
+static apr_status_t ipull_head(h2_ififo *fifo, int *pi, int block)
+{
+    apr_status_t rv;
+    
+    if ((rv = icheck_not_empty(fifo, block)) != APR_SUCCESS) {
+        *pi = 0;
+        return rv;
+    }
+    *pi = fifo->elems[fifo->head];
+    --fifo->count;
+    if (fifo->count > 0) {
+        fifo->head = inth_index(fifo, 1);
+        if (fifo->count+1 == fifo->nelems) {
+            apr_thread_cond_broadcast(fifo->not_full);
+        }
+    }
+    return APR_SUCCESS;
+}
+
+static apr_status_t ififo_pull(h2_ififo *fifo, int *pi, int block)
+{
+    apr_status_t rv;
+    
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+    
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        rv = ipull_head(fifo, pi, block);
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+apr_status_t h2_ififo_pull(h2_ififo *fifo, int *pi)
+{
+    return ififo_pull(fifo, pi, 1);
+}
+
+apr_status_t h2_ififo_try_pull(h2_ififo *fifo, int *pi)
+{
+    return ififo_pull(fifo, pi, 0);
+}
+
+static apr_status_t ififo_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx, int block)
+{
+    apr_status_t rv;
+    int id;
+    
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+    
+    if (APR_SUCCESS == (rv = apr_thread_mutex_lock(fifo->lock))) {
+        if (APR_SUCCESS == (rv = ipull_head(fifo, &id, block))) {
+            switch (fn(id, ctx)) {
+                case H2_FIFO_OP_PULL:
+                    break;
+                case H2_FIFO_OP_REPUSH:
+                    rv = ififo_push_int(fifo, id, block);
+                    break;
+            }
+        }
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
+apr_status_t h2_ififo_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx)
+{
+    return ififo_peek(fifo, fn, ctx, 1);
+}
+
+apr_status_t h2_ififo_try_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx)
+{
+    return ififo_peek(fifo, fn, ctx, 0);
+}
+
+apr_status_t h2_ififo_remove(h2_ififo *fifo, int id)
+{
+    apr_status_t rv;
+    
+    if (fifo->aborted) {
+        return APR_EOF;
+    }
+
+    if ((rv = apr_thread_mutex_lock(fifo->lock)) == APR_SUCCESS) {
+        int i, rc;
+        int e;
+        
+        rc = 0;
+        for (i = 0; i < fifo->count; ++i) {
+            e = fifo->elems[inth_index(fifo, i)];
+            if (e == id) {
+                ++rc;
+            }
+            else if (rc) {
+                fifo->elems[inth_index(fifo, i-rc)] = e;
+            }
+        }
+        if (rc) {
+            fifo->count -= rc;
+            if (fifo->count + rc == fifo->nelems) {
+                apr_thread_cond_broadcast(fifo->not_full);
+            }
+            rv = APR_SUCCESS;
+        }
+        else {
+            rv = APR_EAGAIN;
+        }
+        
+        apr_thread_mutex_unlock(fifo->lock);
+    }
+    return rv;
+}
+
 /*******************************************************************************
  * h2_util for apt_table_t
  ******************************************************************************/
@@ -618,7 +1195,7 @@ static apr_status_t last_not_included(ap
 {
     apr_bucket *b;
     apr_status_t status = APR_SUCCESS;
-    int files_allowed = pfile_buckets_allowed? *pfile_buckets_allowed : 0;
+    int files_allowed = pfile_buckets_allowed? (int)*pfile_buckets_allowed : 0;
     
     if (maxlen >= 0) {
         /* Find the bucket, up to which we reach maxlen/mem bytes */
@@ -653,8 +1230,8 @@ static apr_status_t last_not_included(ap
                      * unless we do not move the file buckets */
                     --files_allowed;
                 }
-                else if (maxlen < b->length) {
-                    apr_bucket_split(b, maxlen);
+                else if (maxlen < (apr_off_t)b->length) {
+                    apr_bucket_split(b, (apr_size_t)maxlen);
                     maxlen = 0;
                 }
                 else {
@@ -671,17 +1248,16 @@ apr_status_t h2_brigade_concat_length(ap
                                       apr_bucket_brigade *src,
                                       apr_off_t length)
 {
-    apr_bucket *b, *next;
+    apr_bucket *b;
     apr_off_t remain = length;
     apr_status_t status = APR_SUCCESS;
     
-    for (b = APR_BRIGADE_FIRST(src); 
-         b != APR_BRIGADE_SENTINEL(src);
-         b = next) {
-        next = APR_BUCKET_NEXT(b);
+    while (!APR_BRIGADE_EMPTY(src)) {
+        b = APR_BRIGADE_FIRST(src); 
         
         if (APR_BUCKET_IS_METADATA(b)) {
-            /* fall through */
+            APR_BUCKET_REMOVE(b);
+            APR_BRIGADE_INSERT_TAIL(dest, b);
         }
         else {
             if (remain == b->length) {
@@ -704,10 +1280,10 @@ apr_status_t h2_brigade_concat_length(ap
                     apr_bucket_split(b, remain);
                 }
             }
+            APR_BUCKET_REMOVE(b);
+            APR_BRIGADE_INSERT_TAIL(dest, b);
+            remain -= b->length;
         }
-        APR_BUCKET_REMOVE(b);
-        APR_BRIGADE_INSERT_TAIL(dest, b);
-        remain -= b->length;
     }
     return status;
 }
@@ -852,7 +1428,7 @@ apr_status_t h2_util_bb_readx(apr_bucket
             
             if (data_len > avail) {
                 apr_bucket_split(b, avail);
-                data_len = avail;
+                data_len = (apr_size_t)avail;
             }
             
             if (consume) {
@@ -892,51 +1468,15 @@ apr_size_t h2_util_bucket_print(char *bu
         off += apr_snprintf(buffer+off, bmax-off, "%s", sep);
     }
     
-    if (APR_BUCKET_IS_METADATA(b)) {
-        if (APR_BUCKET_IS_EOS(b)) {
-            off += apr_snprintf(buffer+off, bmax-off, "eos");
-        }
-        else if (APR_BUCKET_IS_FLUSH(b)) {
-            off += apr_snprintf(buffer+off, bmax-off, "flush");
-        }
-        else if (AP_BUCKET_IS_EOR(b)) {
-            off += apr_snprintf(buffer+off, bmax-off, "eor");
-        }
-        else {
-            off += apr_snprintf(buffer+off, bmax-off, "meta(unknown)");
-        }
+    if (bmax <= off) {
+        return off;
     }
-    else {
-        const char *btype = "data";
-        if (APR_BUCKET_IS_FILE(b)) {
-            btype = "file";
-        }
-        else if (APR_BUCKET_IS_PIPE(b)) {
-            btype = "pipe";
-        }
-        else if (APR_BUCKET_IS_SOCKET(b)) {
-            btype = "socket";
-        }
-        else if (APR_BUCKET_IS_HEAP(b)) {
-            btype = "heap";
-        }
-        else if (APR_BUCKET_IS_TRANSIENT(b)) {
-            btype = "transient";
-        }
-        else if (APR_BUCKET_IS_IMMORTAL(b)) {
-            btype = "immortal";
-        }
-#if APR_HAS_MMAP
-        else if (APR_BUCKET_IS_MMAP(b)) {
-            btype = "mmap";
-        }
-#endif
-        else if (APR_BUCKET_IS_POOL(b)) {
-            btype = "pool";
-        }
-        
+    else if (APR_BUCKET_IS_METADATA(b)) {
+        off += apr_snprintf(buffer+off, bmax-off, "%s", b->type->name);
+    }
+    else if (bmax > off) {
         off += apr_snprintf(buffer+off, bmax-off, "%s[%ld]", 
-                            btype, 
+                            b->type->name, 
                             (long)(b->length == ((apr_size_t)-1)? 
                                    -1 : b->length));
     }
@@ -951,20 +1491,24 @@ apr_size_t h2_util_bb_print(char *buffer
     const char *sp = "";
     apr_bucket *b;
     
-    if (bb) {
-        memset(buffer, 0, bmax--);
-        off += apr_snprintf(buffer+off, bmax-off, "%s(", tag);
-        for (b = APR_BRIGADE_FIRST(bb); 
-             bmax && (b != APR_BRIGADE_SENTINEL(bb));
-             b = APR_BUCKET_NEXT(b)) {
-            
-            off += h2_util_bucket_print(buffer+off, bmax-off, b, sp);
-            sp = " ";
+    if (bmax > 1) {
+        if (bb) {
+            memset(buffer, 0, bmax--);
+            off += apr_snprintf(buffer+off, bmax-off, "%s(", tag);
+            for (b = APR_BRIGADE_FIRST(bb); 
+                 (bmax > off) && (b != APR_BRIGADE_SENTINEL(bb));
+                 b = APR_BUCKET_NEXT(b)) {
+                
+                off += h2_util_bucket_print(buffer+off, bmax-off, b, sp);
+                sp = " ";
+            }
+            if (bmax > off) {
+                off += apr_snprintf(buffer+off, bmax-off, ")%s", sep);
+            }
+        }
+        else {
+            off += apr_snprintf(buffer+off, bmax-off, "%s(null)%s", tag, sep);
         }
-        off += apr_snprintf(buffer+off, bmax-off, ")%s", sep);
-    }
-    else {
-        off += apr_snprintf(buffer+off, bmax-off, "%s(null)%s", tag, sep);
     }
     return off;
 }
@@ -972,7 +1516,8 @@ apr_size_t h2_util_bb_print(char *buffer
 apr_status_t h2_append_brigade(apr_bucket_brigade *to,
                                apr_bucket_brigade *from, 
                                apr_off_t *plen,
-                               int *peos)
+                               int *peos,
+                               h2_bucket_gate *should_append)
 {
     apr_bucket *e;
     apr_off_t len = 0, remain = *plen;
@@ -983,7 +1528,10 @@ apr_status_t h2_append_brigade(apr_bucke
     while (!APR_BRIGADE_EMPTY(from)) {
         e = APR_BRIGADE_FIRST(from);
         
-        if (APR_BUCKET_IS_METADATA(e)) {
+        if (!should_append(e)) {
+            goto leave;
+        }
+        else if (APR_BUCKET_IS_METADATA(e)) {
             if (APR_BUCKET_IS_EOS(e)) {
                 *peos = 1;
                 apr_bucket_delete(e);
@@ -1002,9 +1550,9 @@ apr_status_t h2_append_brigade(apr_bucke
             
             if (remain < e->length) {
                 if (remain <= 0) {
-                    return APR_SUCCESS;
+                    goto leave;
                 }
-                apr_bucket_split(e, remain);
+                apr_bucket_split(e, (apr_size_t)remain);
             }
         }
         
@@ -1013,7 +1561,7 @@ apr_status_t h2_append_brigade(apr_bucke
         len += e->length;
         remain -= e->length;
     }
-    
+leave:
     *plen = len;
     return APR_SUCCESS;
 }
@@ -1061,89 +1609,150 @@ static int count_header(void *ctx, const
     return 1;
 }
 
-#define NV_ADD_LIT_CS(nv, k, v)     add_header(nv, k, sizeof(k) - 1, v, strlen(v))
-#define NV_ADD_CS_CS(nv, k, v)      add_header(nv, k, strlen(k), v, strlen(v))
+static const char *inv_field_name_chr(const char *token)
+{
+    const char *p = ap_scan_http_token(token);
+    if (p == token && *p == ':') {
+        p = ap_scan_http_token(++p);
+    }
+    return (p && *p)? p : NULL;
+}
 
-static int add_header(h2_ngheader *ngh, 
-                      const char *key, size_t key_len,
-                      const char *value, size_t val_len)
+static const char *inv_field_value_chr(const char *token)
 {
-    nghttp2_nv *nv = &ngh->nv[ngh->nvlen++];
-    
+    const char *p = ap_scan_http_field_content(token);
+    return (p && *p)? p : NULL;
+}
+
+typedef struct ngh_ctx {
+    apr_pool_t *p;
+    int unsafe;
+    h2_ngheader *ngh;
+    apr_status_t status;
+} ngh_ctx;
+
+static int add_header(ngh_ctx *ctx, const char *key, const char *value)
+{
+    nghttp2_nv *nv = &(ctx->ngh)->nv[(ctx->ngh)->nvlen++];
+    const char *p;
+
+    if (!ctx->unsafe) {
+        if ((p = inv_field_name_chr(key))) {
+            ap_log_perror(APLOG_MARK, APLOG_TRACE1, APR_EINVAL, ctx->p,
+                          "h2_request: head field '%s: %s' has invalid char %s", 
+                          key, value, p);
+            ctx->status = APR_EINVAL;
+            return 0;
+        }
+        if ((p = inv_field_value_chr(value))) {
+            ap_log_perror(APLOG_MARK, APLOG_TRACE1, APR_EINVAL, ctx->p,
+                          "h2_request: head field '%s: %s' has invalid char %s", 
+                          key, value, p);
+            ctx->status = APR_EINVAL;
+            return 0;
+        }
+    }
     nv->name = (uint8_t*)key;
-    nv->namelen = key_len;
+    nv->namelen = strlen(key);
     nv->value = (uint8_t*)value;
-    nv->valuelen = val_len;
+    nv->valuelen = strlen(value);
+    
     return 1;
 }
 
 static int add_table_header(void *ctx, const char *key, const char *value)
 {
     if (!h2_util_ignore_header(key)) {
-        add_header(ctx, key, strlen(key), value, strlen(value));
+        add_header(ctx, key, value);
     }
     return 1;
 }
 
-
-h2_ngheader *h2_util_ngheader_make(apr_pool_t *p, apr_table_t *header)
+static apr_status_t ngheader_create(h2_ngheader **ph, apr_pool_t *p, 
+                                    int unsafe, size_t key_count, 
+                                    const char *keys[], const char *values[],
+                                    apr_table_t *headers)
 {
-    h2_ngheader *ngh;
-    size_t n;
+    ngh_ctx ctx;
+    size_t n, i;
     
-    n = 0;
-    apr_table_do(count_header, &n, header, NULL);
+    ctx.p = p;
+    ctx.unsafe = unsafe;
+    
+    n = key_count;
+    apr_table_do(count_header, &n, headers, NULL);
+    
+    *ph = ctx.ngh = apr_pcalloc(p, sizeof(h2_ngheader));
+    if (!ctx.ngh) {
+        return APR_ENOMEM;
+    }
+    
+    ctx.ngh->nv =  apr_pcalloc(p, n * sizeof(nghttp2_nv));
+    if (!ctx.ngh->nv) {
+        return APR_ENOMEM;
+    }
+    
+    ctx.status = APR_SUCCESS;
+    for (i = 0; i < key_count; ++i) {
+        if (!add_header(&ctx, keys[i], values[i])) {
+            return ctx.status;
+        }
+    }
     
-    ngh = apr_pcalloc(p, sizeof(h2_ngheader));
-    ngh->nv =  apr_pcalloc(p, n * sizeof(nghttp2_nv));
-    apr_table_do(add_table_header, ngh, header, NULL);
+    apr_table_do(add_table_header, &ctx, headers, NULL);
 
-    return ngh;
+    return ctx.status;
 }
 
-h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, 
-                                       int http_status, 
-                                       apr_table_t *header)
+static int is_unsafe(h2_headers *h)
 {
-    h2_ngheader *ngh;
-    size_t n;
-    
-    n = 1;
-    apr_table_do(count_header, &n, header, NULL);
-    
-    ngh = apr_pcalloc(p, sizeof(h2_ngheader));
-    ngh->nv =  apr_pcalloc(p, n * sizeof(nghttp2_nv));
-    NV_ADD_LIT_CS(ngh, ":status", apr_psprintf(p, "%d", http_status));
-    apr_table_do(add_table_header, ngh, header, NULL);
+    const char *v = apr_table_get(h->notes, H2_HDR_CONFORMANCE);
+    return (v && !strcmp(v, H2_HDR_CONFORMANCE_UNSAFE));
+}
 
-    return ngh;
+apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p, 
+                                    h2_headers *headers)
+{
+    return ngheader_create(ph, p, is_unsafe(headers), 
+                           0, NULL, NULL, headers->headers);
+}
+                                     
+apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p,
+                                    h2_headers *headers) 
+{
+    const char *keys[] = {
+        ":status"
+    };
+    const char *values[] = {
+        apr_psprintf(p, "%d", headers->status)
+    };
+    return ngheader_create(ph, p, is_unsafe(headers),  
+                           H2_ALEN(keys), keys, values, headers->headers);
 }
 
-h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, 
-                                       const struct h2_request *req)
+apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p, 
+                                    const struct h2_request *req)
 {
     
-    h2_ngheader *ngh;
-    size_t n;
+    const char *keys[] = {
+        ":scheme", 
+        ":authority", 
+        ":path", 
+        ":method", 
+    };
+    const char *values[] = {
+        req->scheme,
+        req->authority, 
+        req->path, 
+        req->method, 
+    };
     
-    AP_DEBUG_ASSERT(req);
-    AP_DEBUG_ASSERT(req->scheme);
-    AP_DEBUG_ASSERT(req->authority);
-    AP_DEBUG_ASSERT(req->path);
-    AP_DEBUG_ASSERT(req->method);
-
-    n = 4;
-    apr_table_do(count_header, &n, req->headers, NULL);
-    
-    ngh = apr_pcalloc(p, sizeof(h2_ngheader));
-    ngh->nv =  apr_pcalloc(p, n * sizeof(nghttp2_nv));
-    NV_ADD_LIT_CS(ngh, ":scheme", req->scheme);
-    NV_ADD_LIT_CS(ngh, ":authority", req->authority);
-    NV_ADD_LIT_CS(ngh, ":path", req->path);
-    NV_ADD_LIT_CS(ngh, ":method", req->method);
-    apr_table_do(add_table_header, ngh, req->headers, NULL);
+    ap_assert(req->scheme);
+    ap_assert(req->authority);
+    ap_assert(req->path);
+    ap_assert(req->method);
 
-    return ngh;
+    return ngheader_create(ph, p, 0, H2_ALEN(keys), keys, values, req->headers);
 }
 
 /*******************************************************************************
@@ -1160,7 +1769,6 @@ typedef struct {
 #define H2_LIT_ARGS(a)      (a),H2_ALEN(a)
 
 static literal IgnoredRequestHeaders[] = {
-    H2_DEF_LITERAL("expect"),
     H2_DEF_LITERAL("upgrade"),
     H2_DEF_LITERAL("connection"),
     H2_DEF_LITERAL("keep-alive"),
@@ -1194,15 +1802,12 @@ static literal IgnoredResponseTrailers[]
     H2_DEF_LITERAL("www-authenticate"),
     H2_DEF_LITERAL("proxy-authenticate"),
 };
-static literal IgnoredProxyRespHds[] = {
-    H2_DEF_LITERAL("alt-svc"),
-};
 
 static int ignore_header(const literal *lits, size_t llen,
                          const char *name, size_t nlen)
 {
     const literal *lit;
-    int i;
+    size_t i;
     
     for (i = 0; i < llen; ++i) {
         lit = &lits[i];
@@ -1229,13 +1834,7 @@ int h2_res_ignore_trailer(const char *na
     return ignore_header(H2_LIT_ARGS(IgnoredResponseTrailers), name, len);
 }
 
-int h2_proxy_res_ignore_header(const char *name, size_t len)
-{
-    return (h2_req_ignore_header(name, len) 
-            || ignore_header(H2_LIT_ARGS(IgnoredProxyRespHds), name, len));
-}
-
-apr_status_t h2_headers_add_h1(apr_table_t *headers, apr_pool_t *pool, 
+apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, 
                                const char *name, size_t nlen,
                                const char *value, size_t vlen)
 {
@@ -1276,13 +1875,12 @@ apr_status_t h2_headers_add_h1(apr_table
  * h2 request handling
  ******************************************************************************/
 
-h2_request *h2_req_createn(int id, apr_pool_t *pool, const char *method, 
-                           const char *scheme, const char *authority, 
-                           const char *path, apr_table_t *header, int serialize)
+h2_request *h2_req_create(int id, apr_pool_t *pool, const char *method, 
+                          const char *scheme, const char *authority, 
+                          const char *path, apr_table_t *header, int serialize)
 {
     h2_request *req = apr_pcalloc(pool, sizeof(h2_request));
     
-    req->id             = id;
     req->method         = method;
     req->scheme         = scheme;
     req->authority      = authority;
@@ -1294,49 +1892,6 @@ h2_request *h2_req_createn(int id, apr_p
     return req;
 }
 
-h2_request *h2_req_create(int id, apr_pool_t *pool, int serialize)
-{
-    return h2_req_createn(id, pool, NULL, NULL, NULL, NULL, NULL, serialize);
-}
-
-typedef struct {
-    apr_table_t *headers;
-    apr_pool_t *pool;
-} h1_ctx;
-
-static int set_h1_header(void *ctx, const char *key, const char *value)
-{
-    h1_ctx *x = ctx;
-    size_t klen = strlen(key);
-    if (!h2_req_ignore_header(key, klen)) {
-        h2_headers_add_h1(x->headers, x->pool, key, klen, value, strlen(value));
-    }
-    return 1;
-}
-
-apr_status_t h2_req_make(h2_request *req, apr_pool_t *pool,
-                         const char *method, const char *scheme, 
-                         const char *authority, const char *path, 
-                         apr_table_t *headers)
-{
-    h1_ctx x;
-
-    req->method    = method;
-    req->scheme    = scheme;
-    req->authority = authority;
-    req->path      = path;
-
-    AP_DEBUG_ASSERT(req->scheme);
-    AP_DEBUG_ASSERT(req->authority);
-    AP_DEBUG_ASSERT(req->path);
-    AP_DEBUG_ASSERT(req->method);
-
-    x.pool = pool;
-    x.headers = req->headers;
-    apr_table_do(set_h1_header, &x, headers, NULL);
-    return APR_SUCCESS;
-}
-
 /*******************************************************************************
  * frame logging
  ******************************************************************************/
@@ -1423,11 +1978,11 @@ int h2_util_frame_print(const nghttp2_fr
 /*******************************************************************************
  * push policy
  ******************************************************************************/
-void h2_push_policy_determine(struct h2_request *req, apr_pool_t *p, int push_enabled)
+int h2_push_policy_determine(apr_table_t *headers, apr_pool_t *p, int push_enabled)
 {
     h2_push_policy policy = H2_PUSH_NONE;
     if (push_enabled) {
-        const char *val = apr_table_get(req->headers, "accept-push-policy");
+        const char *val = apr_table_get(headers, "accept-push-policy");
         if (val) {
             if (ap_find_token(p, val, "fast-load")) {
                 policy = H2_PUSH_FAST_LOAD;
@@ -1450,6 +2005,6 @@ void h2_push_policy_determine(struct h2_
             policy = H2_PUSH_DEFAULT;
         }
     }
-    req->push_policy = policy;
+    return policy;
 }
 
diff -up --new-file httpd-2.4.23/modules/http2/h2_util.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_util.h
--- httpd-2.4.23/modules/http2/h2_util.h	2016-06-14 10:51:31.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_util.h	2017-10-13 10:37:45.000000000 +0200
@@ -68,7 +68,6 @@ void h2_ihash_remove_val(h2_ihash_t *ih,
 void h2_ihash_clear(h2_ihash_t *ih);
 
 size_t h2_ihash_shift(h2_ihash_t *ih, void **buffer, size_t max);
-size_t h2_ihash_ishift(h2_ihash_t *ih, int *buffer, size_t max);
 
 /*******************************************************************************
  * iqueue - sorted list of int with user defined ordering
@@ -116,12 +115,22 @@ int h2_iq_count(h2_iqueue *q);
 /**
  * Add a stream id to the queue. 
  *
- * @param q the queue to append the task to
+ * @param q the queue to append the id to
  * @param sid the stream id to add
  * @param cmp the comparator for sorting
- * @param ctx user data for comparator 
+ * @param ctx user data for comparator
+ * @return != 0 iff id was not already there 
  */
-void h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx);
+int h2_iq_add(h2_iqueue *q, int sid, h2_iq_cmp *cmp, void *ctx);
+
+/**
+ * Append the id to the queue if not already present. 
+ *
+ * @param q the queue to append the id to
+ * @param sid the id to append
+ * @return != 0 iff id was not already there 
+ */
+int h2_iq_append(h2_iqueue *q, int sid);
 
 /**
  * Remove the stream id from the queue. Return != 0 iff task
@@ -148,19 +157,175 @@ void h2_iq_clear(h2_iqueue *q);
 void h2_iq_sort(h2_iqueue *q, h2_iq_cmp *cmp, void *ctx);
 
 /**
- * Get the first stream id from the queue or NULL if the queue is empty. 
- * The task will be removed.
+ * Get the first id from the queue or 0 if the queue is empty. 
+ * The id is being removed.
  *
- * @param q the queue to get the first task from
- * @return the first stream id of the queue, 0 if empty
+ * @param q the queue to get the first id from
+ * @return the first id of the queue, 0 if empty
  */
 int h2_iq_shift(h2_iqueue *q);
 
+/**
+ * Get the first max ids from the queue. All these ids will be removed.
+ *
+ * @param q the queue to get the first task from
+ * @param pint the int array to receive the values
+ * @param max the maximum number of ids to shift
+ * @return the actual number of ids shifted
+ */
+size_t h2_iq_mshift(h2_iqueue *q, int *pint, size_t max);
+
+/**
+ * Determine if int is in the queue already
+ *
+ * @parm q the queue
+ * @param sid the integer id to check for
+ * @return != 0 iff sid is already in the queue
+ */
+int h2_iq_contains(h2_iqueue *q, int sid);
+
+/*******************************************************************************
+ * FIFO queue (void* elements)
+ ******************************************************************************/
+
+/**
+ * A thread-safe FIFO queue with some extra bells and whistles, if you
+ * do not need anything special, better use 'apr_queue'.
+ */
+typedef struct h2_fifo h2_fifo;
+
+/**
+ * Create a FIFO queue that can hold up to capacity elements. Elements can
+ * appear several times.
+ */
+apr_status_t h2_fifo_create(h2_fifo **pfifo, apr_pool_t *pool, int capacity);
+
+/**
+ * Create a FIFO set that can hold up to capacity elements. Elements only
+ * appear once. Pushing an element already present does not change the
+ * queue and is successful.
+ */
+apr_status_t h2_fifo_set_create(h2_fifo **pfifo, apr_pool_t *pool, int capacity);
+
+apr_status_t h2_fifo_term(h2_fifo *fifo);
+apr_status_t h2_fifo_interrupt(h2_fifo *fifo);
+
+int h2_fifo_count(h2_fifo *fifo);
+
+/**
+ * Push en element into the queue. Blocks if there is no capacity left.
+ * 
+ * @param fifo the FIFO queue
+ * @param elem the element to push
+ * @return APR_SUCCESS on push, APR_EAGAIN on try_push on a full queue,
+ *         APR_EEXIST when in set mode and elem already there.
+ */
+apr_status_t h2_fifo_push(h2_fifo *fifo, void *elem);
+apr_status_t h2_fifo_try_push(h2_fifo *fifo, void *elem);
+
+apr_status_t h2_fifo_pull(h2_fifo *fifo, void **pelem);
+apr_status_t h2_fifo_try_pull(h2_fifo *fifo, void **pelem);
+
+typedef enum {
+    H2_FIFO_OP_PULL,   /* pull the element from the queue, ie discard it */
+    H2_FIFO_OP_REPUSH, /* pull and immediatley re-push it */
+} h2_fifo_op_t;
+
+typedef h2_fifo_op_t h2_fifo_peek_fn(void *head, void *ctx);
+
+/**
+ * Call given function on the head of the queue, once it exists, and
+ * perform the returned operation on it. The queue will hold its lock during
+ * this time, so no other operations on the queue are possible.
+ * @param fifo the queue to peek at
+ * @param fn   the function to call on the head, once available
+ * @param ctx  context to pass in call to function
+ */
+apr_status_t h2_fifo_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx);
+
+/**
+ * Non-blocking version of h2_fifo_peek.
+ */
+apr_status_t h2_fifo_try_peek(h2_fifo *fifo, h2_fifo_peek_fn *fn, void *ctx);
+
+/**
+ * Remove the elem from the queue, will remove multiple appearances.
+ * @param elem  the element to remove
+ * @return APR_SUCCESS iff > 0 elems were removed, APR_EAGAIN otherwise.
+ */
+apr_status_t h2_fifo_remove(h2_fifo *fifo, void *elem);
+
+/*******************************************************************************
+ * iFIFO queue (int elements)
+ ******************************************************************************/
+
+/**
+ * A thread-safe FIFO queue with some extra bells and whistles, if you
+ * do not need anything special, better use 'apr_queue'.
+ */
+typedef struct h2_ififo h2_ififo;
+
+/**
+ * Create a FIFO queue that can hold up to capacity int. ints can
+ * appear several times.
+ */
+apr_status_t h2_ififo_create(h2_ififo **pfifo, apr_pool_t *pool, int capacity);
+
+/**
+ * Create a FIFO set that can hold up to capacity integers. Ints only
+ * appear once. Pushing an int already present does not change the
+ * queue and is successful.
+ */
+apr_status_t h2_ififo_set_create(h2_ififo **pfifo, apr_pool_t *pool, int capacity);
+
+apr_status_t h2_ififo_term(h2_ififo *fifo);
+apr_status_t h2_ififo_interrupt(h2_ififo *fifo);
+
+int h2_ififo_count(h2_ififo *fifo);
+
+/**
+ * Push an int into the queue. Blocks if there is no capacity left.
+ * 
+ * @param fifo the FIFO queue
+ * @param id  the int to push
+ * @return APR_SUCCESS on push, APR_EAGAIN on try_push on a full queue,
+ *         APR_EEXIST when in set mode and elem already there.
+ */
+apr_status_t h2_ififo_push(h2_ififo *fifo, int id);
+apr_status_t h2_ififo_try_push(h2_ififo *fifo, int id);
+
+apr_status_t h2_ififo_pull(h2_ififo *fifo, int *pi);
+apr_status_t h2_ififo_try_pull(h2_ififo *fifo, int *pi);
+
+typedef h2_fifo_op_t h2_ififo_peek_fn(int head, void *ctx);
+
+/**
+ * Call given function on the head of the queue, once it exists, and
+ * perform the returned operation on it. The queue will hold its lock during
+ * this time, so no other operations on the queue are possible.
+ * @param fifo the queue to peek at
+ * @param fn   the function to call on the head, once available
+ * @param ctx  context to pass in call to function
+ */
+apr_status_t h2_ififo_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx);
+
+/**
+ * Non-blocking version of h2_fifo_peek.
+ */
+apr_status_t h2_ififo_try_peek(h2_ififo *fifo, h2_ififo_peek_fn *fn, void *ctx);
+
+/**
+ * Remove the integer from the queue, will remove multiple appearances.
+ * @param id  the integer to remove
+ * @return APR_SUCCESS iff > 0 ints were removed, APR_EAGAIN otherwise.
+ */
+apr_status_t h2_ififo_remove(h2_ififo *fifo, int id);
+
 /*******************************************************************************
  * common helpers
  ******************************************************************************/
 /* h2_log2(n) iff n is a power of 2 */
-unsigned char h2_log2(apr_uint32_t n);
+unsigned char h2_log2(int n);
 
 /**
  * Count the bytes that all key/value pairs in a table have
@@ -172,15 +337,6 @@ unsigned char h2_log2(apr_uint32_t n);
  */
 apr_size_t h2_util_table_bytes(apr_table_t *t, apr_size_t pair_extra);
 
-/**
- * Return != 0 iff the string s contains the token, as specified in
- * HTTP header syntax, rfc7230.
- */
-int h2_util_contains_token(apr_pool_t *pool, const char *s, const char *token);
-
-const char *h2_util_first_token_match(apr_pool_t *pool, const char *s, 
-                                      const char *tokens[], apr_size_t len);
-
 /** Match a header value against a string constance, case insensitive */
 #define H2_HD_MATCH_LIT(l, name, nlen)  \
     ((nlen == sizeof(l) - 1) && !apr_strnatcasecmp(l, name))
@@ -191,18 +347,18 @@ const char *h2_util_first_token_match(ap
 int h2_req_ignore_header(const char *name, size_t len);
 int h2_req_ignore_trailer(const char *name, size_t len);
 int h2_res_ignore_trailer(const char *name, size_t len);
-int h2_proxy_res_ignore_header(const char *name, size_t len);
 
 /**
  * Set the push policy for the given request. Takes request headers into 
  * account, see draft https://tools.ietf.org/html/draft-ruellan-http-accept-push-policy-00
  * for details.
  * 
- * @param req the request to determine the policy for
+ * @param headers the http headers to inspect
  * @param p the pool to use
  * @param push_enabled if HTTP/2 server push is generally enabled for this request
+ * @return the push policy desired
  */
-void h2_push_policy_determine(struct h2_request *req, apr_pool_t *p, int push_enabled);
+int h2_push_policy_determine(apr_table_t *headers, apr_pool_t *p, int push_enabled);
 
 /*******************************************************************************
  * base64 url encoding, different table from normal base64
@@ -241,19 +397,21 @@ const char *h2_util_base64url_encode(con
 
 int h2_util_ignore_header(const char *name);
 
+struct h2_headers;
+
 typedef struct h2_ngheader {
     nghttp2_nv *nv;
     apr_size_t nvlen;
 } h2_ngheader;
 
-h2_ngheader *h2_util_ngheader_make(apr_pool_t *p, apr_table_t *header);
-h2_ngheader *h2_util_ngheader_make_res(apr_pool_t *p, 
-                                       int http_status, 
-                                       apr_table_t *header);
-h2_ngheader *h2_util_ngheader_make_req(apr_pool_t *p, 
-                                       const struct h2_request *req);
+apr_status_t h2_res_create_ngtrailer(h2_ngheader **ph, apr_pool_t *p, 
+                                     struct h2_headers *headers); 
+apr_status_t h2_res_create_ngheader(h2_ngheader **ph, apr_pool_t *p, 
+                                    struct h2_headers *headers); 
+apr_status_t h2_req_create_ngheader(h2_ngheader **ph, apr_pool_t *p, 
+                                    const struct h2_request *req);
 
-apr_status_t h2_headers_add_h1(apr_table_t *headers, apr_pool_t *pool, 
+apr_status_t h2_req_add_header(apr_table_t *headers, apr_pool_t *pool, 
                                const char *name, size_t nlen,
                                const char *value, size_t vlen);
 
@@ -261,16 +419,10 @@ apr_status_t h2_headers_add_h1(apr_table
  * h2_request helpers
  ******************************************************************************/
 
-struct h2_request *h2_req_createn(int id, apr_pool_t *pool, const char *method, 
-                                  const char *scheme, const char *authority, 
-                                  const char *path, apr_table_t *header,
-                                  int serialize);
-struct h2_request *h2_req_create(int id, apr_pool_t *pool, int serialize);
-
-apr_status_t h2_req_make(struct h2_request *req, apr_pool_t *pool,
-                         const char *method, const char *scheme, 
-                         const char *authority, const char *path, 
-                         apr_table_t *headers);
+struct h2_request *h2_req_create(int id, apr_pool_t *pool, const char *method, 
+                                 const char *scheme, const char *authority, 
+                                 const char *path, apr_table_t *header,
+                                 int serialize);
 
 /*******************************************************************************
  * apr brigade helpers
@@ -358,14 +510,15 @@ do { \
     const char *line = "(null)"; \
     apr_size_t len, bmax = sizeof(buffer)/sizeof(buffer[0]); \
     len = h2_util_bb_print(buffer, bmax, (tag), "", (bb)); \
-    ap_log_cerror(APLOG_MARK, level, 0, (c), "bb_dump(%ld-%d): %s", \
-        (c)->id, (int)(sid), (len? buffer : line)); \
+    ap_log_cerror(APLOG_MARK, level, 0, (c), "bb_dump(%ld): %s", \
+        ((c)->master? (c)->master->id : (c)->id), (len? buffer : line)); \
 } while(0)
 
 
+typedef int h2_bucket_gate(apr_bucket *b);
 /**
  * Transfer buckets from one brigade to another with a limit on the 
- * maximum amount of bytes transfered. Does no setaside magic, lifetime
+ * maximum amount of bytes transferred. Does no setaside magic, lifetime
  * of brigades must fit. 
  * @param to   brigade to transfer buckets to
  * @param from brigades to remove buckets from
@@ -375,7 +528,8 @@ do { \
 apr_status_t h2_append_brigade(apr_bucket_brigade *to,
                                apr_bucket_brigade *from, 
                                apr_off_t *plen,
-                               int *peos);
+                               int *peos,
+                               h2_bucket_gate *should_append);
 
 /**
  * Get an approximnation of the memory footprint of the given
diff -up --new-file httpd-2.4.23/modules/http2/h2_version.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_version.h
--- httpd-2.4.23/modules/http2/h2_version.h	2016-06-15 12:06:21.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_version.h	2017-10-13 10:37:45.000000000 +0200
@@ -26,7 +26,7 @@
  * @macro
  * Version number of the http2 module as c string
  */
-#define MOD_HTTP2_VERSION "1.5.11"
+#define MOD_HTTP2_VERSION "1.10.12"
 
 /**
  * @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 0x01050b
+#define MOD_HTTP2_VERSION_NUM 0x010a0b
 
 
 #endif /* mod_h2_h2_version_h */
diff -up --new-file httpd-2.4.23/modules/http2/h2_worker.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_worker.c
--- httpd-2.4.23/modules/http2/h2_worker.c	2016-05-18 17:10:20.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_worker.c	1970-01-01 01:00:00.000000000 +0100
@@ -1,103 +0,0 @@
-/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <assert.h>
-
-#include <apr_thread_cond.h>
-
-#include <mpm_common.h>
-#include <httpd.h>
-#include <http_core.h>
-#include <http_log.h>
-
-#include "h2.h"
-#include "h2_private.h"
-#include "h2_conn.h"
-#include "h2_ctx.h"
-#include "h2_h2.h"
-#include "h2_mplx.h"
-#include "h2_task.h"
-#include "h2_worker.h"
-
-static void* APR_THREAD_FUNC execute(apr_thread_t *thread, void *wctx)
-{
-    h2_worker *worker = (h2_worker *)wctx;
-    int sticky;
-    
-    while (!worker->aborted) {
-        h2_task *task;
-        
-        /* Get a h2_task from the main workers queue. */
-        worker->get_next(worker, worker->ctx, &task, &sticky);
-        while (task) {
-        
-            h2_task_do(task, thread);
-            /* report the task done and maybe get another one from the same
-             * mplx (= master connection), if we can be sticky. 
-             */
-            if (sticky && !worker->aborted) {
-                h2_mplx_task_done(task->mplx, task, &task);
-            }
-            else {
-                h2_mplx_task_done(task->mplx, task, NULL);
-                task = NULL;
-            }
-        }
-    }
-
-    worker->worker_done(worker, worker->ctx);
-    return NULL;
-}
-
-h2_worker *h2_worker_create(int id,
-                            apr_pool_t *pool,
-                            apr_threadattr_t *attr,
-                            h2_worker_mplx_next_fn *get_next,
-                            h2_worker_done_fn *worker_done,
-                            void *ctx)
-{
-    h2_worker *w = apr_pcalloc(pool, sizeof(h2_worker));
-    if (w) {
-        w->id = id;
-        APR_RING_ELEM_INIT(w, link);
-        w->get_next = get_next;
-        w->worker_done = worker_done;
-        w->ctx = ctx;
-        apr_thread_create(&w->thread, attr, execute, w, pool);
-    }
-    return w;
-}
-
-apr_status_t h2_worker_destroy(h2_worker *worker)
-{
-    if (worker->thread) {
-        apr_status_t status;
-        apr_thread_join(&status, worker->thread);
-        worker->thread = NULL;
-    }
-    return APR_SUCCESS;
-}
-
-void h2_worker_abort(h2_worker *worker)
-{
-    worker->aborted = 1;
-}
-
-int h2_worker_is_aborted(h2_worker *worker)
-{
-    return worker->aborted;
-}
-
-
diff -up --new-file httpd-2.4.23/modules/http2/h2_worker.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_worker.h
--- httpd-2.4.23/modules/http2/h2_worker.h	2016-04-28 14:43:02.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_worker.h	1970-01-01 01:00:00.000000000 +0100
@@ -1,135 +0,0 @@
-/* Copyright 2015 greenbytes GmbH (https://www.greenbytes.de)
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- 
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef __mod_h2__h2_worker__
-#define __mod_h2__h2_worker__
-
-struct h2_mplx;
-struct h2_request;
-struct h2_task;
-
-/* h2_worker is a basically a apr_thread_t that reads fromt he h2_workers
- * task queue and runs h2_tasks it is given.
- */
-typedef struct h2_worker h2_worker;
-
-/* Invoked when the worker wants a new task to process. Will block
- * until a h2_mplx becomes available or the worker itself
- * gets aborted (idle timeout, for example). */
-typedef apr_status_t h2_worker_mplx_next_fn(h2_worker *worker,
-                                            void *ctx,
-                                            struct h2_task **ptask,
-                                            int *psticky);
-
-/* Invoked just before the worker thread exits. */
-typedef void h2_worker_done_fn(h2_worker *worker, void *ctx);
-
-
-struct h2_worker {
-    int id;
-    /** Links to the rest of the workers */
-    APR_RING_ENTRY(h2_worker) link;
-    apr_thread_t *thread;
-    h2_worker_mplx_next_fn *get_next;
-    h2_worker_done_fn *worker_done;
-    void *ctx;
-    int aborted;
-};
-
-/**
- * The magic pointer value that indicates the head of a h2_worker list
- * @param  b The worker list
- * @return The magic pointer value
- */
-#define H2_WORKER_LIST_SENTINEL(b)	APR_RING_SENTINEL((b), h2_worker, link)
-
-/**
- * Determine if the worker list is empty
- * @param b The list to check
- * @return true or false
- */
-#define H2_WORKER_LIST_EMPTY(b)	APR_RING_EMPTY((b), h2_worker, link)
-
-/**
- * Return the first worker in a list
- * @param b The list to query
- * @return The first worker in the list
- */
-#define H2_WORKER_LIST_FIRST(b)	APR_RING_FIRST(b)
-
-/**
- * Return the last worker in a list
- * @param b The list to query
- * @return The last worker int he list
- */
-#define H2_WORKER_LIST_LAST(b)	APR_RING_LAST(b)
-
-/**
- * Insert a single worker at the front of a list
- * @param b The list to add to
- * @param e The worker to insert
- */
-#define H2_WORKER_LIST_INSERT_HEAD(b, e) do {				\
-	h2_worker *ap__b = (e);                                        \
-	APR_RING_INSERT_HEAD((b), ap__b, h2_worker, link);	\
-    } while (0)
-
-/**
- * Insert a single worker at the end of a list
- * @param b The list to add to
- * @param e The worker to insert
- */
-#define H2_WORKER_LIST_INSERT_TAIL(b, e) do {				\
-	h2_worker *ap__b = (e);					\
-	APR_RING_INSERT_TAIL((b), ap__b, h2_worker, link);	\
-    } while (0)
-
-/**
- * Get the next worker in the list
- * @param e The current worker
- * @return The next worker
- */
-#define H2_WORKER_NEXT(e)	APR_RING_NEXT((e), link)
-/**
- * Get the previous worker in the list
- * @param e The current worker
- * @return The previous worker
- */
-#define H2_WORKER_PREV(e)	APR_RING_PREV((e), link)
-
-/**
- * Remove a worker from its list
- * @param e The worker to remove
- */
-#define H2_WORKER_REMOVE(e)	APR_RING_REMOVE((e), link)
-
-
-/* Create a new worker with given id, pool and attributes, callbacks
- * callback parameter.
- */
-h2_worker *h2_worker_create(int id,
-                            apr_pool_t *pool,
-                            apr_threadattr_t *attr,
-                            h2_worker_mplx_next_fn *get_next,
-                            h2_worker_done_fn *worker_done,
-                            void *ctx);
-
-apr_status_t h2_worker_destroy(h2_worker *worker);
-
-void h2_worker_abort(h2_worker *worker);
-
-int h2_worker_is_aborted(h2_worker *worker);
-
-#endif /* defined(__mod_h2__h2_worker__) */
diff -up --new-file httpd-2.4.23/modules/http2/h2_workers.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_workers.c
--- httpd-2.4.23/modules/http2/h2_workers.c	2016-04-28 14:43:02.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_workers.c	2017-10-13 10:37:45.000000000 +0200
@@ -27,224 +27,265 @@
 #include "h2_private.h"
 #include "h2_mplx.h"
 #include "h2_task.h"
-#include "h2_worker.h"
 #include "h2_workers.h"
+#include "h2_util.h"
 
+typedef struct h2_slot h2_slot;
+struct h2_slot {
+    int id;
+    h2_slot *next;
+    h2_workers *workers;
+    int aborted;
+    int sticks;
+    h2_task *task;
+    apr_thread_t *thread;
+    apr_thread_mutex_t *lock;
+    apr_thread_cond_t *not_idle;
+};
 
-static int in_list(h2_workers *workers, h2_mplx *m)
+static h2_slot *pop_slot(h2_slot **phead) 
 {
-    h2_mplx *e;
-    for (e = H2_MPLX_LIST_FIRST(&workers->mplxs); 
-         e != H2_MPLX_LIST_SENTINEL(&workers->mplxs);
-         e = H2_MPLX_NEXT(e)) {
-        if (e == m) {
-            return 1;
+    /* Atomically pop a slot from the list */
+    for (;;) {
+        h2_slot *first = *phead;
+        if (first == NULL) {
+            return NULL;
+        }
+        if (apr_atomic_casptr((void*)phead, first->next, first) == first) {
+            first->next = NULL;
+            return first;
         }
     }
-    return 0;
 }
 
-static void cleanup_zombies(h2_workers *workers, int lock)
+static void push_slot(h2_slot **phead, h2_slot *slot)
 {
-    if (lock) {
-        apr_thread_mutex_lock(workers->lock);
-    }
-    while (!H2_WORKER_LIST_EMPTY(&workers->zombies)) {
-        h2_worker *zombie = H2_WORKER_LIST_FIRST(&workers->zombies);
-        H2_WORKER_REMOVE(zombie);
-        ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
-                      "h2_workers: cleanup zombie %d", zombie->id);
-        h2_worker_destroy(zombie);
-    }
-    if (lock) {
-        apr_thread_mutex_unlock(workers->lock);
+    /* Atomically push a slot to the list */
+    ap_assert(!slot->next);
+    for (;;) {
+        h2_slot *next = slot->next = *phead;
+        if (apr_atomic_casptr((void*)phead, slot, next) == next) {
+            return;
+        }
     }
 }
 
-static h2_task *next_task(h2_workers *workers)
+static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx);
+
+static apr_status_t activate_slot(h2_workers *workers, h2_slot *slot) 
 {
-    h2_task *task = NULL;
-    h2_mplx *last = NULL;
-    int has_more;
+    apr_status_t status;
     
-    /* Get the next h2_mplx to process that has a task to hand out.
-     * If it does, place it at the end of the queu and return the
-     * task to the worker.
-     * If it (currently) has no tasks, remove it so that it needs
-     * to register again for scheduling.
-     * If we run out of h2_mplx in the queue, we need to wait for
-     * new mplx to arrive. Depending on how many workers do exist,
-     * we do a timed wait or block indefinitely.
-     */
-    while (!task && !H2_MPLX_LIST_EMPTY(&workers->mplxs)) {
-        h2_mplx *m = H2_MPLX_LIST_FIRST(&workers->mplxs);
-        
-        if (last == m) {
-            break;
+    slot->workers = workers;
+    slot->aborted = 0;
+    slot->task = NULL;
+
+    if (!slot->lock) {
+        status = apr_thread_mutex_create(&slot->lock,
+                                         APR_THREAD_MUTEX_DEFAULT,
+                                         workers->pool);
+        if (status != APR_SUCCESS) {
+            push_slot(&workers->free, slot);
+            return status;
         }
-        H2_MPLX_REMOVE(m);
-        --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;
-            if (!last) {
-                last = m;
-            }
+    }
+
+    if (!slot->not_idle) {
+        status = apr_thread_cond_create(&slot->not_idle, workers->pool);
+        if (status != APR_SUCCESS) {
+            push_slot(&workers->free, slot);
+            return status;
         }
     }
-    return task;
+    
+    ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, workers->s,
+                 "h2_workers: new thread for slot %d", slot->id); 
+    /* thread will either immediately start work or add itself
+     * to the idle queue */
+    apr_thread_create(&slot->thread, workers->thread_attr, slot_run, slot, 
+                      workers->pool);
+    if (!slot->thread) {
+        push_slot(&workers->free, slot);
+        return APR_ENOMEM;
+    }
+    
+    apr_atomic_inc32(&workers->worker_count);
+    return APR_SUCCESS;
+}
+
+static apr_status_t add_worker(h2_workers *workers)
+{
+    h2_slot *slot = pop_slot(&workers->free);
+    if (slot) {
+        return activate_slot(workers, slot);
+    }
+    return APR_EAGAIN;
+}
+
+static void wake_idle_worker(h2_workers *workers) 
+{
+    h2_slot *slot = pop_slot(&workers->idle);
+    if (slot) {
+        apr_thread_mutex_lock(slot->lock);
+        apr_thread_cond_signal(slot->not_idle);
+        apr_thread_mutex_unlock(slot->lock);
+    }
+    else if (workers->dynamic) {
+        add_worker(workers);
+    }
+}
+
+static void cleanup_zombies(h2_workers *workers)
+{
+    h2_slot *slot;
+    while ((slot = pop_slot(&workers->zombies))) {
+        if (slot->thread) {
+            apr_status_t status;
+            apr_thread_join(&status, slot->thread);
+            slot->thread = NULL;
+        }
+        apr_atomic_dec32(&workers->worker_count);
+        slot->next = NULL;
+        push_slot(&workers->free, slot);
+    }
+}
+
+static apr_status_t slot_pull_task(h2_slot *slot, h2_mplx *m)
+{
+    apr_status_t rv;
+    
+    rv = h2_mplx_pop_task(m, &slot->task);
+    if (slot->task) {
+        /* Ok, we got something to give back to the worker for execution. 
+         * If we still have idle workers, we let the worker be sticky, 
+         * e.g. making it poll the task's h2_mplx instance for more work 
+         * before asking back here. */
+        slot->sticks = slot->workers->max_workers;
+        return rv;            
+    }
+    slot->sticks = 0;
+    return APR_EOF;
+}
+
+static h2_fifo_op_t mplx_peek(void *head, void *ctx)
+{
+    h2_mplx *m = head;
+    h2_slot *slot = ctx;
+    
+    if (slot_pull_task(slot, m) == APR_EAGAIN) {
+        wake_idle_worker(slot->workers);
+        return H2_FIFO_OP_REPUSH;
+    } 
+    return H2_FIFO_OP_PULL;
 }
 
 /**
  * Get the next task for the given worker. Will block until a task arrives
  * or the max_wait timer expires and more than min workers exist.
  */
-static apr_status_t get_mplx_next(h2_worker *worker, void *ctx, 
-                                  h2_task **ptask, int *psticky)
+static apr_status_t get_next(h2_slot *slot)
 {
+    h2_workers *workers = slot->workers;
     apr_status_t status;
-    apr_time_t wait_until = 0, now;
-    h2_workers *workers = ctx;
-    h2_task *task = NULL;
     
-    *ptask = NULL;
-    *psticky = 0;
-    
-    status = apr_thread_mutex_lock(workers->lock);
-    if (status == APR_SUCCESS) {
-        ++workers->idle_workers;
-        ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
-                     "h2_worker(%d): looking for work", worker->id);
-        
-        while (!h2_worker_is_aborted(worker) && !workers->aborted
-               && !(task = next_task(workers))) {
-        
-            /* Need to wait for a new tasks to arrive. If we are above
-             * minimum workers, we do a timed wait. When timeout occurs
-             * and we have still more workers, we shut down one after
-             * the other. */
-            cleanup_zombies(workers, 0);
-            if (workers->worker_count > workers->min_workers) {
-                now = apr_time_now();
-                if (now >= wait_until) {
-                    wait_until = now + apr_time_from_sec(workers->max_idle_secs);
-                }
-                
-                ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
-                             "h2_worker(%d): waiting signal, "
-                             "workers=%d, idle=%d", worker->id, 
-                             (int)workers->worker_count, 
-                             workers->idle_workers);
-                status = apr_thread_cond_timedwait(workers->mplx_added,
-                                                   workers->lock, 
-                                                   wait_until - now);
-                if (status == APR_TIMEUP
-                    && workers->worker_count > workers->min_workers) {
-                    /* waited long enough without getting a task and
-                     * we are above min workers, abort this one. */
-                    ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, 
-                                 workers->s,
-                                 "h2_workers: aborting idle worker");
-                    h2_worker_abort(worker);
-                    break;
-                }
-            }
-            else {
-                ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
-                             "h2_worker(%d): waiting signal (eternal), "
-                             "worker_count=%d, idle=%d", worker->id, 
-                             (int)workers->worker_count,
-                             workers->idle_workers);
-                apr_thread_cond_wait(workers->mplx_added, workers->lock);
+    slot->task = NULL;
+    while (!slot->aborted) {
+        if (!slot->task) {
+            status = h2_fifo_try_peek(workers->mplxs, mplx_peek, slot);
+            if (status == APR_EOF) {
+                return status;
             }
         }
         
-        /* Here, we either have gotten task or decided to shut down
-         * the calling worker.
-         */
-        if (task) {
-            /* Ok, we got something to give back to the worker for execution. 
-             * If we have more idle workers than h2_mplx in our queue, then
-             * we let the worker be sticky, e.g. making it poll the task's
-             * h2_mplx instance for more work before asking back here.
-             * This avoids entering our global lock as long as enough idle
-             * workers remain. Stickiness of a worker ends when the connection
-             * has no new tasks to process, so the worker will get back here
-             * eventually.
-             */
-            *ptask = task;
-            *psticky = (workers->max_workers >= workers->mplx_count);
-            
-            if (workers->mplx_count && workers->idle_workers > 1) {
-                apr_thread_cond_signal(workers->mplx_added);
-            }
+        if (slot->task) {
+            return APR_SUCCESS;
         }
         
-        --workers->idle_workers;
-        apr_thread_mutex_unlock(workers->lock);
+        cleanup_zombies(workers);
+
+        apr_thread_mutex_lock(slot->lock);
+        push_slot(&workers->idle, slot);
+        apr_thread_cond_wait(slot->not_idle, slot->lock);
+        apr_thread_mutex_unlock(slot->lock);
     }
-    
-    return *ptask? APR_SUCCESS : APR_EOF;
+    return APR_EOF;
 }
 
-static void worker_done(h2_worker *worker, void *ctx)
+static void slot_done(h2_slot *slot)
 {
-    h2_workers *workers = ctx;
-    apr_status_t status = apr_thread_mutex_lock(workers->lock);
-    if (status == APR_SUCCESS) {
-        ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
-                     "h2_worker(%d): done", worker->id);
-        H2_WORKER_REMOVE(worker);
-        --workers->worker_count;
-        H2_WORKER_LIST_INSERT_TAIL(&workers->zombies, worker);
-        
-        apr_thread_mutex_unlock(workers->lock);
-    }
+    push_slot(&(slot->workers->zombies), slot);
 }
 
-static apr_status_t add_worker(h2_workers *workers)
+
+static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx)
 {
-    h2_worker *w = h2_worker_create(workers->next_worker_id++,
-                                    workers->pool, workers->thread_attr,
-                                    get_mplx_next, worker_done, workers);
-    if (!w) {
-        return APR_ENOMEM;
+    h2_slot *slot = wctx;
+    
+    while (!slot->aborted) {
+
+        /* Get a h2_task from the mplxs queue. */
+        get_next(slot);
+        while (slot->task) {
+        
+            h2_task_do(slot->task, thread, slot->id);
+            
+            /* Report the task as done. If stickyness is left, offer the
+             * mplx the opportunity to give us back a new task right away.
+             */
+            if (!slot->aborted && (--slot->sticks > 0)) {
+                h2_mplx_task_done(slot->task->mplx, slot->task, &slot->task);
+            }
+            else {
+                h2_mplx_task_done(slot->task->mplx, slot->task, NULL);
+                slot->task = NULL;
+            }
+        }
     }
-    ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
-                 "h2_workers: adding worker(%d)", w->id);
-    ++workers->worker_count;
-    H2_WORKER_LIST_INSERT_TAIL(&workers->workers, w);
-    return APR_SUCCESS;
+
+    slot_done(slot);
+    return NULL;
 }
 
-static apr_status_t h2_workers_start(h2_workers *workers)
+static apr_status_t workers_pool_cleanup(void *data)
 {
-    apr_status_t status = apr_thread_mutex_lock(workers->lock);
-    if (status == APR_SUCCESS) {
-        ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
-                      "h2_workers: starting");
-
-        while (workers->worker_count < workers->min_workers
-               && status == APR_SUCCESS) {
-            status = add_worker(workers);
+    h2_workers *workers = data;
+    h2_slot *slot;
+    
+    if (!workers->aborted) {
+        workers->aborted = 1;
+        /* abort all idle slots */
+        for (;;) {
+            slot = pop_slot(&workers->idle);
+            if (slot) {
+                apr_thread_mutex_lock(slot->lock);
+                slot->aborted = 1;
+                apr_thread_cond_signal(slot->not_idle);
+                apr_thread_mutex_unlock(slot->lock);
+            }
+            else {
+                break;
+            }
         }
-        apr_thread_mutex_unlock(workers->lock);
+
+        h2_fifo_term(workers->mplxs);
+        h2_fifo_interrupt(workers->mplxs);
+
+        cleanup_zombies(workers);
     }
-    return status;
+    return APR_SUCCESS;
 }
 
 h2_workers *h2_workers_create(server_rec *s, apr_pool_t *server_pool,
                               int min_workers, int max_workers,
-                              apr_size_t max_tx_handles)
+                              int idle_secs)
 {
     apr_status_t status;
     h2_workers *workers;
     apr_pool_t *pool;
+    int i, n;
 
-    AP_DEBUG_ASSERT(s);
-    AP_DEBUG_ASSERT(server_pool);
+    ap_assert(s);
+    ap_assert(server_pool);
 
     /* let's have our own pool that will be parent to all h2_worker
      * instances we create. This happens in various threads, but always
@@ -254,163 +295,77 @@ h2_workers *h2_workers_create(server_rec
     apr_pool_create(&pool, server_pool);
     apr_pool_tag(pool, "h2_workers");
     workers = apr_pcalloc(pool, sizeof(h2_workers));
-    if (workers) {
-        workers->s = s;
-        workers->pool = pool;
-        workers->min_workers = min_workers;
-        workers->max_workers = max_workers;
-        workers->max_idle_secs = 10;
-        
-        workers->max_tx_handles = max_tx_handles;
-        workers->spare_tx_handles = workers->max_tx_handles;
-        
-        apr_threadattr_create(&workers->thread_attr, workers->pool);
-        if (ap_thread_stacksize != 0) {
-            apr_threadattr_stacksize_set(workers->thread_attr,
-                                         ap_thread_stacksize);
-            ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
-                         "h2_workers: using stacksize=%ld", 
-                         (long)ap_thread_stacksize);
-        }
-        
-        APR_RING_INIT(&workers->workers, h2_worker, link);
-        APR_RING_INIT(&workers->zombies, h2_worker, link);
-        APR_RING_INIT(&workers->mplxs, h2_mplx, link);
-        
-        status = apr_thread_mutex_create(&workers->lock,
-                                         APR_THREAD_MUTEX_DEFAULT,
-                                         workers->pool);
-        if (status == APR_SUCCESS) {
-            status = apr_thread_cond_create(&workers->mplx_added, workers->pool);
-        }
-        
-        if (status == APR_SUCCESS) {
-            status = apr_thread_mutex_create(&workers->tx_lock,
-                                             APR_THREAD_MUTEX_DEFAULT,
-                                             workers->pool);
-        }
-        
-        if (status == APR_SUCCESS) {
-            status = h2_workers_start(workers);
-        }
-        
-        if (status != APR_SUCCESS) {
-            h2_workers_destroy(workers);
-            workers = NULL;
-        }
+    if (!workers) {
+        return NULL;
     }
-    return workers;
-}
-
-void h2_workers_destroy(h2_workers *workers)
-{
-    /* before we go, cleanup any zombie workers that may have accumulated */
-    cleanup_zombies(workers, 1);
     
-    if (workers->mplx_added) {
-        apr_thread_cond_destroy(workers->mplx_added);
-        workers->mplx_added = NULL;
-    }
-    if (workers->lock) {
-        apr_thread_mutex_destroy(workers->lock);
-        workers->lock = NULL;
-    }
-    while (!H2_MPLX_LIST_EMPTY(&workers->mplxs)) {
-        h2_mplx *m = H2_MPLX_LIST_FIRST(&workers->mplxs);
-        H2_MPLX_REMOVE(m);
+    workers->s = s;
+    workers->pool = pool;
+    workers->min_workers = min_workers;
+    workers->max_workers = max_workers;
+    workers->max_idle_secs = (idle_secs > 0)? idle_secs : 10;
+
+    status = h2_fifo_create(&workers->mplxs, pool, 2 * workers->max_workers);
+    if (status != APR_SUCCESS) {
+        return NULL;
     }
-    while (!H2_WORKER_LIST_EMPTY(&workers->workers)) {
-        h2_worker *w = H2_WORKER_LIST_FIRST(&workers->workers);
-        H2_WORKER_REMOVE(w);
+    
+    status = apr_threadattr_create(&workers->thread_attr, workers->pool);
+    if (status != APR_SUCCESS) {
+        return NULL;
     }
-    if (workers->pool) {
-        apr_pool_destroy(workers->pool);
-        /* workers is gone */
+    
+    if (ap_thread_stacksize != 0) {
+        apr_threadattr_stacksize_set(workers->thread_attr,
+                                     ap_thread_stacksize);
+        ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, s,
+                     "h2_workers: using stacksize=%ld", 
+                     (long)ap_thread_stacksize);
     }
-}
-
-apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m)
-{
-    apr_status_t status = apr_thread_mutex_lock(workers->lock);
-    if (status == APR_SUCCESS) {
-        ap_log_error(APLOG_MARK, APLOG_TRACE3, status, workers->s,
-                     "h2_workers: register mplx(%ld), idle=%d", 
-                     m->id, workers->idle_workers);
-        if (in_list(workers, m)) {
-            status = APR_EAGAIN;
-        }
-        else {
-            H2_MPLX_LIST_INSERT_TAIL(&workers->mplxs, m);
-            ++workers->mplx_count;
-            status = APR_SUCCESS;
-        }
-        
-        if (workers->idle_workers > 0) { 
-            apr_thread_cond_signal(workers->mplx_added);
+    
+    status = apr_thread_mutex_create(&workers->lock,
+                                     APR_THREAD_MUTEX_DEFAULT,
+                                     workers->pool);
+    if (status == APR_SUCCESS) {        
+        n = workers->nslots = workers->max_workers;
+        workers->slots = apr_pcalloc(workers->pool, n * sizeof(h2_slot));
+        if (workers->slots == NULL) {
+            workers->nslots = 0;
+            status = APR_ENOMEM;
         }
-        else if (status == APR_SUCCESS 
-                 && workers->worker_count < workers->max_workers) {
-            ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
-                         "h2_workers: got %d worker, adding 1", 
-                         workers->worker_count);
-            add_worker(workers);
+        for (i = 0; i < n; ++i) {
+            workers->slots[i].id = i;
         }
-        apr_thread_mutex_unlock(workers->lock);
     }
-    return status;
-}
-
-apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m)
-{
-    apr_status_t status = apr_thread_mutex_lock(workers->lock);
     if (status == APR_SUCCESS) {
-        status = APR_EAGAIN;
-        if (in_list(workers, m)) {
-            H2_MPLX_REMOVE(m);
-            status = APR_SUCCESS;
+        /* we activate all for now, TODO: support min_workers again.
+         * do this in reverse for vanity reasons so slot 0 will most
+         * likely be at head of idle queue. */
+        n = workers->max_workers;
+        for (i = n-1; i >= 0; --i) {
+            status = activate_slot(workers, &workers->slots[i]);
+        }
+        /* the rest of the slots go on the free list */
+        for(i = n; i < workers->nslots; ++i) {
+            push_slot(&workers->free, &workers->slots[i]);
         }
-        apr_thread_mutex_unlock(workers->lock);
+        workers->dynamic = (workers->worker_count < workers->max_workers);
     }
-    return status;
-}
-
-void h2_workers_set_max_idle_secs(h2_workers *workers, int idle_secs)
-{
-    if (idle_secs <= 0) {
-        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, workers->s,
-                     APLOGNO(02962) "h2_workers: max_worker_idle_sec value of %d"
-                     " is not valid, ignored.", idle_secs);
-        return;
+    if (status == APR_SUCCESS) {
+        apr_pool_pre_cleanup_register(pool, workers, workers_pool_cleanup);    
+        return workers;
     }
-    workers->max_idle_secs = idle_secs;
+    return NULL;
 }
 
-apr_size_t h2_workers_tx_reserve(h2_workers *workers, apr_size_t count)
+apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m)
 {
-    apr_status_t status = apr_thread_mutex_lock(workers->tx_lock);
-    if (status == APR_SUCCESS) {
-        count = H2MIN(workers->spare_tx_handles, count);
-        workers->spare_tx_handles -= count;
-        ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, workers->s,
-                     "h2_workers: reserved %d tx handles, %d/%d left", 
-                     (int)count, (int)workers->spare_tx_handles,
-                     (int)workers->max_tx_handles);
-        apr_thread_mutex_unlock(workers->tx_lock);
-        return count;
-    }
-    return 0;
+    apr_status_t status = h2_fifo_push(workers->mplxs, m);
+    wake_idle_worker(workers);
+    return status;
 }
 
-void h2_workers_tx_free(h2_workers *workers, apr_size_t count)
+apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m)
 {
-    apr_status_t status = apr_thread_mutex_lock(workers->tx_lock);
-    if (status == APR_SUCCESS) {
-        workers->spare_tx_handles += count;
-        ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, workers->s,
-                     "h2_workers: freed %d tx handles, %d/%d left", 
-                     (int)count, (int)workers->spare_tx_handles,
-                     (int)workers->max_tx_handles);
-        apr_thread_mutex_unlock(workers->tx_lock);
-    }
+    return h2_fifo_remove(workers->mplxs, m);
 }
-
diff -up --new-file httpd-2.4.23/modules/http2/h2_workers.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_workers.h
--- httpd-2.4.23/modules/http2/h2_workers.h	2016-03-02 12:21:28.000000000 +0100
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/h2_workers.h	2017-04-10 17:04:55.000000000 +0200
@@ -27,6 +27,9 @@ struct apr_thread_cond_t;
 struct h2_mplx;
 struct h2_request;
 struct h2_task;
+struct h2_fifo;
+
+struct h2_slot;
 
 typedef struct h2_workers h2_workers;
 
@@ -37,26 +40,24 @@ struct h2_workers {
     int next_worker_id;
     int min_workers;
     int max_workers;
-    int worker_count;
-    int idle_workers;
     int max_idle_secs;
     
-    apr_size_t max_tx_handles;
-    apr_size_t spare_tx_handles;
-    
-    unsigned int aborted : 1;
+    int aborted;
+    int dynamic;
 
     apr_threadattr_t *thread_attr;
+    int nslots;
+    struct h2_slot *slots;
+    
+    volatile apr_uint32_t worker_count;
+    
+    struct h2_slot *free;
+    struct h2_slot *idle;
+    struct h2_slot *zombies;
     
-    APR_RING_HEAD(h2_worker_list, h2_worker) workers;
-    APR_RING_HEAD(h2_worker_zombies, h2_worker) zombies;
-    APR_RING_HEAD(h2_mplx_list, h2_mplx) mplxs;
-    int mplx_count;
+    struct h2_fifo *mplxs;
     
     struct apr_thread_mutex_t *lock;
-    struct apr_thread_cond_t *mplx_added;
-
-    struct apr_thread_mutex_t *tx_lock;
 };
 
 
@@ -64,12 +65,7 @@ struct h2_workers {
  * threads.
  */
 h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pool,
-                              int min_size, int max_size, 
-                              apr_size_t max_tx_handles);
-
-/* Destroy the worker pool and all its threads. 
- */
-void h2_workers_destroy(h2_workers *workers);
+                              int min_size, int max_size, int idle_secs);
 
 /**
  * Registers a h2_mplx for task scheduling. If this h2_mplx runs
@@ -83,38 +79,4 @@ apr_status_t h2_workers_register(h2_work
  */
 apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m);
 
-/**
- * Set the amount of seconds a h2_worker should wait for new tasks
- * before shutting down (if there are more than the minimum number of
- * workers).
- */
-void h2_workers_set_max_idle_secs(h2_workers *workers, int idle_secs);
-
-/**
- * Reservation of file handles available for transfer between workers
- * and master connections. 
- *
- * When handling output from request processing, file handles are often 
- * encountered when static files are served. The most efficient way is then
- * to forward the handle itself to the master connection where it can be
- * read or sendfile'd to the client. But file handles are a scarce resource,
- * so there needs to be a limit on how many handles are transferred this way.
- *
- * h2_workers keeps track of the number of reserved handles and observes a
- * configurable maximum value. 
- *
- * @param workers the workers instance
- * @param count how many handles the caller wishes to reserve
- * @return the number of reserved handles, may be 0.
- */
-apr_size_t h2_workers_tx_reserve(h2_workers *workers, apr_size_t count);
-
-/**
- * Return a number of reserved file handles back to the pool. The number
- * overall may not exceed the numbers reserved.
- * @param workers the workers instance
- * @param count how many handles are returned to the pool
- */
-void h2_workers_tx_free(h2_workers *workers, apr_size_t count);
-
 #endif /* defined(__mod_h2__h2_workers__) */
diff -up --new-file httpd-2.4.23/modules/http2/mod_http2.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/mod_http2.c
--- httpd-2.4.23/modules/http2/mod_http2.c	2016-04-28 14:43:02.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/mod_http2.c	2017-07-04 14:34:15.000000000 +0200
@@ -47,10 +47,10 @@ static void h2_hooks(apr_pool_t *pool);
 
 AP_DECLARE_MODULE(http2) = {
     STANDARD20_MODULE_STUFF,
-    NULL,
-    NULL,
+    h2_config_create_dir, /* func to create per dir config */
+    h2_config_merge_dir,  /* func to merge per dir config */
     h2_config_create_svr, /* func to create per server config */
-    h2_config_merge,      /* func to merge per server config */
+    h2_config_merge_svr,  /* func to merge per server config */
     h2_cmds,              /* command handlers */
     h2_hooks
 };
@@ -60,9 +60,12 @@ static int h2_h2_fixups(request_rec *r);
 typedef struct {
     unsigned int change_prio : 1;
     unsigned int sha256 : 1;
+    unsigned int inv_headers : 1;
+    unsigned int dyn_windows : 1;
 } features;
 
 static features myfeats;
+static int mpm_warned;
 
 /* The module initialization. Called once as apache hook, before any multi
  * processing (threaded or not) happens. It is typically at least called twice, 
@@ -84,16 +87,20 @@ static int h2_post_config(apr_pool_t *p,
     const char *mod_h2_init_key = "mod_http2_init_counter";
     nghttp2_info *ngh2;
     apr_status_t status;
-    const char *sep = "";
     
     (void)plog;(void)ptemp;
 #ifdef H2_NG2_CHANGE_PRIO
     myfeats.change_prio = 1;
-    sep = "+";
 #endif
 #ifdef H2_OPENSSL
     myfeats.sha256 = 1;
 #endif
+#ifdef H2_NG2_INVALID_HEADER_CB
+    myfeats.inv_headers = 1;
+#endif
+#ifdef H2_NG2_LOCAL_WIN_SIZE
+    myfeats.dyn_windows = 1;
+#endif
     
     apr_pool_userdata_get(&data, mod_h2_init_key, s->process->pool);
     if ( data == NULL ) {
@@ -106,10 +113,12 @@ static int h2_post_config(apr_pool_t *p,
     
     ngh2 = nghttp2_version(0);
     ap_log_error( APLOG_MARK, APLOG_INFO, 0, s, APLOGNO(03090)
-                 "mod_http2 (v%s, feats=%s%s%s, nghttp2 %s), initializing...",
+                 "mod_http2 (v%s, feats=%s%s%s%s, nghttp2 %s), initializing...",
                  MOD_HTTP2_VERSION, 
-                 myfeats.change_prio? "CHPRIO" : "", sep, 
-                 myfeats.sha256?      "SHA256" : "",
+                 myfeats.change_prio? "CHPRIO"  : "", 
+                 myfeats.sha256?      "+SHA256" : "",
+                 myfeats.inv_headers? "+INVHD"  : "",
+                 myfeats.dyn_windows? "+DWINS"  : "",
                  ngh2?                ngh2->version_str : "unknown");
     
     switch (h2_conn_mpm_type()) {
@@ -133,6 +142,17 @@ static int h2_post_config(apr_pool_t *p,
             break;
     }
     
+    if (!h2_mpm_supported() && !mpm_warned) {
+        mpm_warned = 1;
+        ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10034)
+                     "The mpm module (%s) is not supported by mod_http2. The mpm determines "
+                     "how things are processed in your server. HTTP/2 has more demands in "
+                     "this regard and the currently selected mpm will just not do. "
+                     "This is an advisory warning. Your server will continue to work, but "
+                     "the HTTP/2 protocol will be inactive.", 
+                     h2_conn_mpm_name());
+    }
+    
     status = h2_h2_init(p, s);
     if (status == APR_SUCCESS) {
         status = h2_switch_init(p, s);
@@ -157,15 +177,16 @@ static apr_status_t http2_req_engine_pus
 
 static apr_status_t http2_req_engine_pull(h2_req_engine *ngn, 
                                           apr_read_type_e block, 
-                                          apr_uint32_t capacity, 
+                                          int capacity, 
                                           request_rec **pr)
 {
     return h2_mplx_req_engine_pull(ngn, block, capacity, pr);
 }
 
-static void http2_req_engine_done(h2_req_engine *ngn, conn_rec *r_conn)
+static void http2_req_engine_done(h2_req_engine *ngn, conn_rec *r_conn,
+                                  apr_status_t status)
 {
-    h2_mplx_req_engine_done(ngn, r_conn);
+    h2_mplx_req_engine_done(ngn, r_conn, status);
 }
 
 /* Runs once per created child process. Perform any process 
@@ -230,8 +251,11 @@ static const char *val_H2_PUSH(apr_pool_
     if (ctx) {
         if (r) {
             h2_task *task = h2_ctx_get_task(ctx);
-            if (task && task->request->push_policy != H2_PUSH_NONE) {
-                return "on";
+            if (task) {
+                h2_stream *stream = h2_mplx_stream_get(task->mplx, task->stream_id);
+                if (stream && stream->push_policy != H2_PUSH_NONE) {
+                    return "on";
+                }
             }
         }
         else if (c && h2_session_push_enabled(ctx->session)) {
@@ -265,7 +289,10 @@ static const char *val_H2_PUSHED_ON(apr_
     if (ctx) {
         h2_task *task = h2_ctx_get_task(ctx);
         if (task && !H2_STREAM_CLIENT_INITIATED(task->stream_id)) {
-            return apr_itoa(p, task->request->initiated_on);
+            h2_stream *stream = h2_mplx_stream_get(task->mplx, task->stream_id);
+            if (stream) {
+                return apr_itoa(p, stream->initiated_on);
+            }
         }
     }
     return "";
@@ -334,7 +361,7 @@ static char *http2_var_lookup(apr_pool_t
             return (char *)vdef->lookup(p, s, c, r, ctx);
         }
     }
-    return "";
+    return (char*)"";
 }
 
 static int h2_h2_fixups(request_rec *r)
diff -up --new-file httpd-2.4.23/modules/http2/mod_http2.dep /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/mod_http2.dep
--- httpd-2.4.23/modules/http2/mod_http2.dep	2016-04-28 23:18:43.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/mod_http2.dep	2017-04-01 03:22:41.000000000 +0200
@@ -57,57 +57,6 @@
 	".\h2_util.h"\
 	
 
-./h2_bucket_eoc.c : \
-	"..\..\include\ap_config.h"\
-	"..\..\include\ap_config_layout.h"\
-	"..\..\include\ap_expr.h"\
-	"..\..\include\ap_hooks.h"\
-	"..\..\include\ap_mmn.h"\
-	"..\..\include\ap_regex.h"\
-	"..\..\include\ap_release.h"\
-	"..\..\include\apache_noprobes.h"\
-	"..\..\include\http_config.h"\
-	"..\..\include\http_connection.h"\
-	"..\..\include\http_core.h"\
-	"..\..\include\http_log.h"\
-	"..\..\include\httpd.h"\
-	"..\..\include\os.h"\
-	"..\..\include\util_cfgtree.h"\
-	"..\..\include\util_filter.h"\
-	"..\..\srclib\apr-util\include\apr_buckets.h"\
-	"..\..\srclib\apr-util\include\apr_hooks.h"\
-	"..\..\srclib\apr-util\include\apr_optional.h"\
-	"..\..\srclib\apr-util\include\apr_optional_hooks.h"\
-	"..\..\srclib\apr-util\include\apr_queue.h"\
-	"..\..\srclib\apr-util\include\apr_uri.h"\
-	"..\..\srclib\apr-util\include\apu.h"\
-	"..\..\srclib\apr\include\apr.h"\
-	"..\..\srclib\apr\include\apr_allocator.h"\
-	"..\..\srclib\apr\include\apr_errno.h"\
-	"..\..\srclib\apr\include\apr_file_info.h"\
-	"..\..\srclib\apr\include\apr_file_io.h"\
-	"..\..\srclib\apr\include\apr_general.h"\
-	"..\..\srclib\apr\include\apr_hash.h"\
-	"..\..\srclib\apr\include\apr_inherit.h"\
-	"..\..\srclib\apr\include\apr_mmap.h"\
-	"..\..\srclib\apr\include\apr_network_io.h"\
-	"..\..\srclib\apr\include\apr_poll.h"\
-	"..\..\srclib\apr\include\apr_pools.h"\
-	"..\..\srclib\apr\include\apr_ring.h"\
-	"..\..\srclib\apr\include\apr_tables.h"\
-	"..\..\srclib\apr\include\apr_thread_mutex.h"\
-	"..\..\srclib\apr\include\apr_thread_proc.h"\
-	"..\..\srclib\apr\include\apr_time.h"\
-	"..\..\srclib\apr\include\apr_user.h"\
-	"..\..\srclib\apr\include\apr_want.h"\
-	".\h2.h"\
-	".\h2_bucket_eoc.h"\
-	".\h2_conn_io.h"\
-	".\h2_mplx.h"\
-	".\h2_private.h"\
-	".\h2_session.h"\
-	
-
 ./h2_bucket_eos.c : \
 	"..\..\include\ap_config.h"\
 	"..\..\include\ap_config_layout.h"\
@@ -283,7 +232,6 @@
 	".\h2_stream.h"\
 	".\h2_task.h"\
 	".\h2_version.h"\
-	".\h2_worker.h"\
 	".\h2_workers.h"\
 	
 
@@ -339,7 +287,6 @@
 	"..\..\srclib\apr\include\apr_user.h"\
 	"..\..\srclib\apr\include\apr_want.h"\
 	".\h2.h"\
-	".\h2_bucket_eoc.h"\
 	".\h2_bucket_eos.h"\
 	".\h2_config.h"\
 	".\h2_conn_io.h"\
@@ -454,7 +401,6 @@
 	".\h2_private.h"\
 	".\h2_push.h"\
 	".\h2_request.h"\
-	".\h2_response.h"\
 	".\h2_session.h"\
 	".\h2_stream.h"\
 	".\h2_task.h"\
@@ -517,7 +463,6 @@
 	".\h2.h"\
 	".\h2_from_h1.h"\
 	".\h2_private.h"\
-	".\h2_response.h"\
 	".\h2_task.h"\
 	".\h2_util.h"\
 	
@@ -648,7 +593,6 @@
 	".\h2_mplx.h"\
 	".\h2_private.h"\
 	".\h2_request.h"\
-	".\h2_response.h"\
 	".\h2_task.h"\
 	".\h2_util.h"\
 	
@@ -753,11 +697,9 @@
 	".\h2_ngn_shed.h"\
 	".\h2_private.h"\
 	".\h2_request.h"\
-	".\h2_response.h"\
 	".\h2_stream.h"\
 	".\h2_task.h"\
 	".\h2_util.h"\
-	".\h2_worker.h"\
 	".\h2_workers.h"\
 	".\mod_http2.h"\
 	
@@ -815,7 +757,6 @@
 	".\h2_ngn_shed.h"\
 	".\h2_private.h"\
 	".\h2_request.h"\
-	".\h2_response.h"\
 	".\h2_task.h"\
 	".\h2_util.h"\
 	".\mod_http2.h"\
@@ -870,7 +811,6 @@
 	".\h2_private.h"\
 	".\h2_push.h"\
 	".\h2_request.h"\
-	".\h2_response.h"\
 	".\h2_session.h"\
 	".\h2_stream.h"\
 	".\h2_util.h"\
@@ -937,58 +877,6 @@
 	".\h2_util.h"\
 	
 
-./h2_response.c : \
-	"..\..\include\ap_config.h"\
-	"..\..\include\ap_config_layout.h"\
-	"..\..\include\ap_expr.h"\
-	"..\..\include\ap_hooks.h"\
-	"..\..\include\ap_mmn.h"\
-	"..\..\include\ap_regex.h"\
-	"..\..\include\ap_release.h"\
-	"..\..\include\apache_noprobes.h"\
-	"..\..\include\http_config.h"\
-	"..\..\include\http_core.h"\
-	"..\..\include\http_log.h"\
-	"..\..\include\httpd.h"\
-	"..\..\include\os.h"\
-	"..\..\include\util_cfgtree.h"\
-	"..\..\include\util_filter.h"\
-	"..\..\include\util_time.h"\
-	"..\..\srclib\apr-util\include\apr_buckets.h"\
-	"..\..\srclib\apr-util\include\apr_hooks.h"\
-	"..\..\srclib\apr-util\include\apr_optional.h"\
-	"..\..\srclib\apr-util\include\apr_optional_hooks.h"\
-	"..\..\srclib\apr-util\include\apr_uri.h"\
-	"..\..\srclib\apr-util\include\apu.h"\
-	"..\..\srclib\apr\include\apr.h"\
-	"..\..\srclib\apr\include\apr_allocator.h"\
-	"..\..\srclib\apr\include\apr_errno.h"\
-	"..\..\srclib\apr\include\apr_file_info.h"\
-	"..\..\srclib\apr\include\apr_file_io.h"\
-	"..\..\srclib\apr\include\apr_general.h"\
-	"..\..\srclib\apr\include\apr_hash.h"\
-	"..\..\srclib\apr\include\apr_inherit.h"\
-	"..\..\srclib\apr\include\apr_mmap.h"\
-	"..\..\srclib\apr\include\apr_network_io.h"\
-	"..\..\srclib\apr\include\apr_poll.h"\
-	"..\..\srclib\apr\include\apr_pools.h"\
-	"..\..\srclib\apr\include\apr_ring.h"\
-	"..\..\srclib\apr\include\apr_strings.h"\
-	"..\..\srclib\apr\include\apr_tables.h"\
-	"..\..\srclib\apr\include\apr_thread_mutex.h"\
-	"..\..\srclib\apr\include\apr_thread_proc.h"\
-	"..\..\srclib\apr\include\apr_time.h"\
-	"..\..\srclib\apr\include\apr_user.h"\
-	"..\..\srclib\apr\include\apr_want.h"\
-	".\h2.h"\
-	".\h2_filter.h"\
-	".\h2_h2.h"\
-	".\h2_private.h"\
-	".\h2_request.h"\
-	".\h2_response.h"\
-	".\h2_util.h"\
-	
-
 ./h2_session.c : \
 	"..\..\include\ap_config.h"\
 	"..\..\include\ap_config_layout.h"\
@@ -1042,7 +930,6 @@
 	"..\..\srclib\apr\include\apr_user.h"\
 	"..\..\srclib\apr\include\apr_want.h"\
 	".\h2.h"\
-	".\h2_bucket_eoc.h"\
 	".\h2_bucket_eos.h"\
 	".\h2_config.h"\
 	".\h2_conn_io.h"\
@@ -1054,7 +941,6 @@
 	".\h2_private.h"\
 	".\h2_push.h"\
 	".\h2_request.h"\
-	".\h2_response.h"\
 	".\h2_session.h"\
 	".\h2_stream.h"\
 	".\h2_task.h"\
@@ -1117,7 +1003,6 @@
 	".\h2_private.h"\
 	".\h2_push.h"\
 	".\h2_request.h"\
-	".\h2_response.h"\
 	".\h2_session.h"\
 	".\h2_stream.h"\
 	".\h2_task.h"\
@@ -1251,7 +1136,6 @@
 	".\h2_session.h"\
 	".\h2_stream.h"\
 	".\h2_task.h"\
-	".\h2_worker.h"\
 	
 
 ./h2_task_input.c : \
@@ -1361,7 +1245,6 @@
 	".\h2_mplx.h"\
 	".\h2_private.h"\
 	".\h2_request.h"\
-	".\h2_response.h"\
 	".\h2_session.h"\
 	".\h2_stream.h"\
 	".\h2_task.h"\
@@ -1417,67 +1300,6 @@
 	".\h2_util.h"\
 	
 
-./h2_worker.c : \
-	"..\..\include\ap_config.h"\
-	"..\..\include\ap_config_layout.h"\
-	"..\..\include\ap_expr.h"\
-	"..\..\include\ap_hooks.h"\
-	"..\..\include\ap_mmn.h"\
-	"..\..\include\ap_mpm.h"\
-	"..\..\include\ap_regex.h"\
-	"..\..\include\ap_release.h"\
-	"..\..\include\apache_noprobes.h"\
-	"..\..\include\http_config.h"\
-	"..\..\include\http_core.h"\
-	"..\..\include\http_log.h"\
-	"..\..\include\httpd.h"\
-	"..\..\include\mpm_common.h"\
-	"..\..\include\os.h"\
-	"..\..\include\scoreboard.h"\
-	"..\..\include\util_cfgtree.h"\
-	"..\..\include\util_filter.h"\
-	"..\..\srclib\apr-util\include\apr_buckets.h"\
-	"..\..\srclib\apr-util\include\apr_hooks.h"\
-	"..\..\srclib\apr-util\include\apr_optional.h"\
-	"..\..\srclib\apr-util\include\apr_optional_hooks.h"\
-	"..\..\srclib\apr-util\include\apr_queue.h"\
-	"..\..\srclib\apr-util\include\apr_uri.h"\
-	"..\..\srclib\apr-util\include\apu.h"\
-	"..\..\srclib\apr\include\apr.h"\
-	"..\..\srclib\apr\include\apr_allocator.h"\
-	"..\..\srclib\apr\include\apr_dso.h"\
-	"..\..\srclib\apr\include\apr_errno.h"\
-	"..\..\srclib\apr\include\apr_file_info.h"\
-	"..\..\srclib\apr\include\apr_file_io.h"\
-	"..\..\srclib\apr\include\apr_general.h"\
-	"..\..\srclib\apr\include\apr_global_mutex.h"\
-	"..\..\srclib\apr\include\apr_hash.h"\
-	"..\..\srclib\apr\include\apr_inherit.h"\
-	"..\..\srclib\apr\include\apr_mmap.h"\
-	"..\..\srclib\apr\include\apr_network_io.h"\
-	"..\..\srclib\apr\include\apr_poll.h"\
-	"..\..\srclib\apr\include\apr_pools.h"\
-	"..\..\srclib\apr\include\apr_portable.h"\
-	"..\..\srclib\apr\include\apr_proc_mutex.h"\
-	"..\..\srclib\apr\include\apr_ring.h"\
-	"..\..\srclib\apr\include\apr_shm.h"\
-	"..\..\srclib\apr\include\apr_tables.h"\
-	"..\..\srclib\apr\include\apr_thread_cond.h"\
-	"..\..\srclib\apr\include\apr_thread_mutex.h"\
-	"..\..\srclib\apr\include\apr_thread_proc.h"\
-	"..\..\srclib\apr\include\apr_time.h"\
-	"..\..\srclib\apr\include\apr_user.h"\
-	"..\..\srclib\apr\include\apr_want.h"\
-	".\h2.h"\
-	".\h2_conn.h"\
-	".\h2_ctx.h"\
-	".\h2_h2.h"\
-	".\h2_mplx.h"\
-	".\h2_private.h"\
-	".\h2_task.h"\
-	".\h2_worker.h"\
-	
-
 ./h2_workers.c : \
 	"..\..\include\ap_config.h"\
 	"..\..\include\ap_config_layout.h"\
@@ -1534,7 +1356,6 @@
 	".\h2_mplx.h"\
 	".\h2_private.h"\
 	".\h2_task.h"\
-	".\h2_worker.h"\
 	".\h2_workers.h"\
 	
 
diff -up --new-file httpd-2.4.23/modules/http2/mod_http2.dsp /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/mod_http2.dsp
--- httpd-2.4.23/modules/http2/mod_http2.dsp	2016-04-28 14:43:02.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/mod_http2.dsp	2017-04-01 03:22:41.000000000 +0200
@@ -109,10 +109,6 @@ SOURCE=./h2_bucket_beam.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_bucket_eoc.c
-# End Source File
-# Begin Source File
-
 SOURCE=./h2_bucket_eos.c
 # End Source File
 # Begin Source File
@@ -161,7 +157,7 @@ SOURCE=./h2_request.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_response.c
+SOURCE=./h2_headers.c
 # End Source File
 # Begin Source File
 
@@ -185,10 +181,6 @@ SOURCE=./h2_util.c
 # End Source File
 # Begin Source File
 
-SOURCE=./h2_worker.c
-# End Source File
-# Begin Source File
-
 SOURCE=./h2_workers.c
 # End Source File
 # Begin Source File
diff -up --new-file httpd-2.4.23/modules/http2/mod_http2.h /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/mod_http2.h
--- httpd-2.4.23/modules/http2/mod_http2.h	2016-05-23 12:55:29.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/mod_http2.h	2016-11-01 21:24:52.000000000 +0100
@@ -49,7 +49,7 @@ typedef apr_status_t http2_req_engine_in
                                            const char *id, 
                                            const char *type,
                                            apr_pool_t *pool, 
-                                           apr_uint32_t req_buffer_size,
+                                           apr_size_t req_buffer_size,
                                            request_rec *r,
                                            http2_output_consumed **pconsumed,
                                            void **pbaton);
@@ -75,8 +75,9 @@ APR_DECLARE_OPTIONAL_FN(apr_status_t,
 /**
  * Get a new request for processing in this engine.
  * @param engine      the engine which is done processing the slave
- * @param timeout     wait a maximum amount of time for a new slave, 0 will not wait
- * @param pslave      the slave connection that needs processing or NULL
+ * @param block       if call should block waiting for request to come
+ * @param capacity    how many parallel requests are acceptable
+ * @param pr          the request that needs processing or NULL
  * @return APR_SUCCESS if new request was assigned
  *         APR_EAGAIN  if no new request is available
  *         APR_EOF          if engine may shut down, as no more request will be scheduled
@@ -85,9 +86,10 @@ APR_DECLARE_OPTIONAL_FN(apr_status_t,
 APR_DECLARE_OPTIONAL_FN(apr_status_t, 
                         http2_req_engine_pull, (h2_req_engine *engine, 
                                                 apr_read_type_e block,
-                                                apr_uint32_t capacity,
+                                                int capacity,
                                                 request_rec **pr));
 APR_DECLARE_OPTIONAL_FN(void, 
                         http2_req_engine_done, (h2_req_engine *engine, 
-                                                conn_rec *rconn));
+                                                conn_rec *rconn,
+                                                apr_status_t status));
 #endif
diff -up --new-file httpd-2.4.23/modules/http2/mod_http2.mak /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/mod_http2.mak
--- httpd-2.4.23/modules/http2/mod_http2.mak	2016-04-28 23:18:43.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/mod_http2.mak	2017-04-01 03:22:41.000000000 +0200
@@ -51,7 +51,6 @@ CLEAN :
 !ENDIF 
 	-@erase "$(INTDIR)\h2_alt_svc.obj"
 	-@erase "$(INTDIR)\h2_bucket_beam.obj"
-	-@erase "$(INTDIR)\h2_bucket_eoc.obj"
 	-@erase "$(INTDIR)\h2_bucket_eos.obj"
 	-@erase "$(INTDIR)\h2_config.obj"
 	-@erase "$(INTDIR)\h2_conn.obj"
@@ -60,17 +59,16 @@ CLEAN :
 	-@erase "$(INTDIR)\h2_filter.obj"
 	-@erase "$(INTDIR)\h2_from_h1.obj"
 	-@erase "$(INTDIR)\h2_h2.obj"
+	-@erase "$(INTDIR)\h2_headers.obj"
 	-@erase "$(INTDIR)\h2_mplx.obj"
 	-@erase "$(INTDIR)\h2_ngn_shed.obj"
 	-@erase "$(INTDIR)\h2_push.obj"
 	-@erase "$(INTDIR)\h2_request.obj"
-	-@erase "$(INTDIR)\h2_response.obj"
 	-@erase "$(INTDIR)\h2_session.obj"
 	-@erase "$(INTDIR)\h2_stream.obj"
 	-@erase "$(INTDIR)\h2_switch.obj"
 	-@erase "$(INTDIR)\h2_task.obj"
 	-@erase "$(INTDIR)\h2_util.obj"
-	-@erase "$(INTDIR)\h2_worker.obj"
 	-@erase "$(INTDIR)\h2_workers.obj"
 	-@erase "$(INTDIR)\mod_http2.obj"
 	-@erase "$(INTDIR)\mod_http2.res"
@@ -130,7 +128,6 @@ LINK32_FLAGS=kernel32.lib nghttp2.lib /n
 LINK32_OBJS= \
 	"$(INTDIR)\h2_alt_svc.obj" \
 	"$(INTDIR)\h2_bucket_beam.obj" \
-	"$(INTDIR)\h2_bucket_eoc.obj" \
 	"$(INTDIR)\h2_bucket_eos.obj" \
 	"$(INTDIR)\h2_config.obj" \
 	"$(INTDIR)\h2_conn.obj" \
@@ -139,17 +136,16 @@ LINK32_OBJS= \
 	"$(INTDIR)\h2_filter.obj" \
 	"$(INTDIR)\h2_from_h1.obj" \
 	"$(INTDIR)\h2_h2.obj" \
+	"$(INTDIR)\h2_headers.obj" \
 	"$(INTDIR)\h2_mplx.obj" \
 	"$(INTDIR)\h2_ngn_shed.obj" \
 	"$(INTDIR)\h2_push.obj" \
 	"$(INTDIR)\h2_request.obj" \
-	"$(INTDIR)\h2_response.obj" \
 	"$(INTDIR)\h2_session.obj" \
 	"$(INTDIR)\h2_stream.obj" \
 	"$(INTDIR)\h2_switch.obj" \
 	"$(INTDIR)\h2_task.obj" \
 	"$(INTDIR)\h2_util.obj" \
-	"$(INTDIR)\h2_worker.obj" \
 	"$(INTDIR)\h2_workers.obj" \
 	"$(INTDIR)\mod_http2.obj" \
 	"$(INTDIR)\mod_http2.res" \
@@ -201,7 +197,6 @@ CLEAN :
 !ENDIF 
 	-@erase "$(INTDIR)\h2_alt_svc.obj"
 	-@erase "$(INTDIR)\h2_bucket_beam.obj"
-	-@erase "$(INTDIR)\h2_bucket_eoc.obj"
 	-@erase "$(INTDIR)\h2_bucket_eos.obj"
 	-@erase "$(INTDIR)\h2_config.obj"
 	-@erase "$(INTDIR)\h2_conn.obj"
@@ -210,17 +205,16 @@ CLEAN :
 	-@erase "$(INTDIR)\h2_filter.obj"
 	-@erase "$(INTDIR)\h2_from_h1.obj"
 	-@erase "$(INTDIR)\h2_h2.obj"
+	-@erase "$(INTDIR)\h2_headers.obj"
 	-@erase "$(INTDIR)\h2_mplx.obj"
 	-@erase "$(INTDIR)\h2_ngn_shed.obj"
 	-@erase "$(INTDIR)\h2_push.obj"
 	-@erase "$(INTDIR)\h2_request.obj"
-	-@erase "$(INTDIR)\h2_response.obj"
 	-@erase "$(INTDIR)\h2_session.obj"
 	-@erase "$(INTDIR)\h2_stream.obj"
 	-@erase "$(INTDIR)\h2_switch.obj"
 	-@erase "$(INTDIR)\h2_task.obj"
 	-@erase "$(INTDIR)\h2_util.obj"
-	-@erase "$(INTDIR)\h2_worker.obj"
 	-@erase "$(INTDIR)\h2_workers.obj"
 	-@erase "$(INTDIR)\mod_http2.obj"
 	-@erase "$(INTDIR)\mod_http2.res"
@@ -280,7 +274,6 @@ LINK32_FLAGS=kernel32.lib nghttp2d.lib /
 LINK32_OBJS= \
 	"$(INTDIR)\h2_alt_svc.obj" \
 	"$(INTDIR)\h2_bucket_beam.obj" \
-	"$(INTDIR)\h2_bucket_eoc.obj" \
 	"$(INTDIR)\h2_bucket_eos.obj" \
 	"$(INTDIR)\h2_config.obj" \
 	"$(INTDIR)\h2_conn.obj" \
@@ -289,17 +282,16 @@ LINK32_OBJS= \
 	"$(INTDIR)\h2_filter.obj" \
 	"$(INTDIR)\h2_from_h1.obj" \
 	"$(INTDIR)\h2_h2.obj" \
+	"$(INTDIR)\h2_headers.obj" \
 	"$(INTDIR)\h2_mplx.obj" \
 	"$(INTDIR)\h2_ngn_shed.obj" \
 	"$(INTDIR)\h2_push.obj" \
 	"$(INTDIR)\h2_request.obj" \
-	"$(INTDIR)\h2_response.obj" \
 	"$(INTDIR)\h2_session.obj" \
 	"$(INTDIR)\h2_stream.obj" \
 	"$(INTDIR)\h2_switch.obj" \
 	"$(INTDIR)\h2_task.obj" \
 	"$(INTDIR)\h2_util.obj" \
-	"$(INTDIR)\h2_worker.obj" \
 	"$(INTDIR)\h2_workers.obj" \
 	"$(INTDIR)\mod_http2.obj" \
 	"$(INTDIR)\mod_http2.res" \
@@ -427,11 +419,6 @@ SOURCE=./h2_bucket_beam.c
 "$(INTDIR)/h2_bucket_beam.obj" : $(SOURCE) "$(INTDIR)"
 
 
-SOURCE=./h2_bucket_eoc.c
-
-"$(INTDIR)\h2_bucket_eoc.obj" : $(SOURCE) "$(INTDIR)"
-
-
 SOURCE=./h2_bucket_eos.c
 
 "$(INTDIR)\h2_bucket_eos.obj" : $(SOURCE) "$(INTDIR)"
@@ -472,6 +459,11 @@ SOURCE=./h2_h2.c
 "$(INTDIR)\h2_h2.obj" : $(SOURCE) "$(INTDIR)"
 
 
+SOURCE=./h2_headers.c
+
+"$(INTDIR)\h2_headers.obj" : $(SOURCE) "$(INTDIR)"
+
+
 SOURCE=./h2_mplx.c
 
 "$(INTDIR)\h2_mplx.obj" : $(SOURCE) "$(INTDIR)"
@@ -492,11 +484,6 @@ SOURCE=./h2_request.c
 "$(INTDIR)\h2_request.obj" : $(SOURCE) "$(INTDIR)"
 
 
-SOURCE=./h2_response.c
-
-"$(INTDIR)\h2_response.obj" : $(SOURCE) "$(INTDIR)"
-
-
 SOURCE=./h2_session.c
 
 "$(INTDIR)\h2_session.obj" : $(SOURCE) "$(INTDIR)"
@@ -522,11 +509,6 @@ SOURCE=./h2_util.c
 "$(INTDIR)\h2_util.obj" : $(SOURCE) "$(INTDIR)"
 
 
-SOURCE=./h2_worker.c
-
-"$(INTDIR)\h2_worker.obj" : $(SOURCE) "$(INTDIR)"
-
-
 SOURCE=./h2_workers.c
 
 "$(INTDIR)\h2_workers.obj" : $(SOURCE) "$(INTDIR)"
diff -up --new-file httpd-2.4.23/modules/http2/mod_proxy_http2.c /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/mod_proxy_http2.c
--- httpd-2.4.23/modules/http2/mod_proxy_http2.c	2016-06-28 15:36:22.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/mod_proxy_http2.c	2017-04-10 17:04:55.000000000 +0200
@@ -26,6 +26,8 @@
 #include "h2_version.h"
 #include "h2_proxy_session.h"
 
+#define H2MIN(x,y) ((x) < (y) ? (x) : (y))
+
 static void register_hook(apr_pool_t *p);
 
 AP_DECLARE_MODULE(proxy_http2) = {
@@ -44,9 +46,10 @@ static apr_status_t (*req_engine_push)(c
                                        http2_req_engine_init *einit);
 static apr_status_t (*req_engine_pull)(h2_req_engine *engine, 
                                        apr_read_type_e block, 
-                                       apr_uint32_t capacity, 
+                                       int capacity, 
                                        request_rec **pr);
-static void (*req_engine_done)(h2_req_engine *engine, conn_rec *r_conn);
+static void (*req_engine_done)(h2_req_engine *engine, conn_rec *r_conn,
+                               apr_status_t status);
                                        
 typedef struct h2_proxy_ctx {
     conn_rec *owner;
@@ -63,9 +66,9 @@ typedef struct h2_proxy_ctx {
     const char *engine_id;
     const char *engine_type;
     apr_pool_t *engine_pool;    
-    apr_uint32_t req_buffer_size;
-    request_rec *next;
-    apr_size_t capacity;
+    apr_size_t req_buffer_size;
+    h2_proxy_fifo *requests;
+    int capacity;
     
     unsigned standalone : 1;
     unsigned is_ssl : 1;
@@ -168,7 +171,7 @@ static int proxy_http2_canon(request_rec
             path = url;   /* this is the raw path */
         }
         else {
-            path = ap_proxy_canonenc(r->pool, url, strlen(url),
+            path = ap_proxy_canonenc(r->pool, url, (int)strlen(url),
                                      enc_path, 0, r->proxyreq);
             search = r->args;
         }
@@ -210,43 +213,30 @@ static apr_status_t proxy_engine_init(h2
                                         const char *id, 
                                         const char *type,
                                         apr_pool_t *pool, 
-                                        apr_uint32_t req_buffer_size,
+                                        apr_size_t req_buffer_size,
                                         request_rec *r,
                                         http2_output_consumed **pconsumed,
                                         void **pctx)
 {
     h2_proxy_ctx *ctx = ap_get_module_config(r->connection->conn_config, 
                                              &proxy_http2_module);
-    if (ctx) {
-        conn_rec *c = ctx->owner;
-        h2_proxy_ctx *nctx;
-        
-        /* we need another lifetime for this. If we do not host
-         * an engine, the context lives in r->pool. Since we expect
-         * to server more than r, we need to live longer */
-        nctx = apr_pcalloc(pool, sizeof(*nctx));
-        if (nctx == NULL) {
-            return APR_ENOMEM;
-        }
-        memcpy(nctx, ctx, sizeof(*nctx));
-        ctx = nctx;
-        ctx->pool = pool;
-        ctx->engine = engine;
-        ctx->engine_id = id;
-        ctx->engine_type = type;
-        ctx->engine_pool = pool;
-        ctx->req_buffer_size = req_buffer_size;
-        ctx->capacity = 100;
-
-        ap_set_module_config(c->conn_config, &proxy_http2_module, ctx);
-
-        *pconsumed = out_consumed;
-        *pctx = ctx;
-        return APR_SUCCESS;
-    }
-    ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03368)
-                  "h2_proxy_session, engine init, no ctx found");
-    return APR_ENOTIMPL;
+    if (!ctx) {
+        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(03368)
+                      "h2_proxy_session, engine init, no ctx found");
+        return APR_ENOTIMPL;
+    }
+    
+    ctx->pool = pool;
+    ctx->engine = engine;
+    ctx->engine_id = id;
+    ctx->engine_type = type;
+    ctx->engine_pool = pool;
+    ctx->req_buffer_size = req_buffer_size;
+    ctx->capacity = H2MIN(100, h2_proxy_fifo_capacity(ctx->requests));
+    
+    *pconsumed = out_consumed;
+    *pctx = ctx;
+    return APR_SUCCESS;
 }
 
 static apr_status_t add_request(h2_proxy_session *session, request_rec *r)
@@ -269,65 +259,74 @@ static apr_status_t add_request(h2_proxy
     return status;
 }
 
-static void request_done(h2_proxy_session *session, request_rec *r,
-                         int complete, int touched)
+static void request_done(h2_proxy_ctx *ctx, request_rec *r,
+                         apr_status_t status, int touched)
 {   
-    h2_proxy_ctx *ctx = session->user_data;
     const char *task_id = apr_table_get(r->connection->notes, H2_TASK_ID_NOTE);
-    
-    if (!complete && !touched) {
-        /* untouched request, need rescheduling */
-        if (req_engine_push && is_h2 && is_h2(ctx->owner)) {
-            if (req_engine_push(ctx->engine_type, r, NULL) == APR_SUCCESS) {
-                /* push to engine */
-                ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, r->connection, 
-                              APLOGNO(03369)
-                              "h2_proxy_session(%s): rescheduled request %s",
-                              ctx->engine_id, task_id);
-                return;
-            }
-        }
-    }
-    
-    if (r == ctx->rbase && complete) {
-        ctx->r_status = APR_SUCCESS;
-    }
-    
-    if (complete) {
-        if (req_engine_done && ctx->engine) {
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, r->connection, 
-                          APLOGNO(03370)
-                          "h2_proxy_session(%s): finished request %s",
+
+    ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, r->connection, 
+                  "h2_proxy_session(%s): request done %s, touched=%d",
+                  ctx->engine_id, task_id, touched);
+    if (status != APR_SUCCESS) {
+        if (!touched) {
+            /* untouched request, need rescheduling */
+            status = h2_proxy_fifo_push(ctx->requests, r);
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, r->connection, 
+                          APLOGNO(03369)
+                          "h2_proxy_session(%s): rescheduled request %s",
                           ctx->engine_id, task_id);
-            req_engine_done(ctx->engine, r->connection);
+            return;
         }
-    }
-    else {
-        if (req_engine_done && ctx->engine) {
-            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, r->connection, 
-                          APLOGNO(03371)
-                          "h2_proxy_session(%s): failed request %s",
-                          ctx->engine_id, task_id);
-            req_engine_done(ctx->engine, r->connection);
+        else {
+            const char *uri;
+            uri = apr_uri_unparse(r->pool, &r->parsed_uri, 0);
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, r->connection, 
+                          APLOGNO(03471) "h2_proxy_session(%s): request %s -> %s "
+                          "not complete, cannot repeat", 
+                          ctx->engine_id, task_id, uri);
         }
     }
+    
+    if (r == ctx->rbase) {
+        ctx->r_status = ((status == APR_SUCCESS)? APR_SUCCESS
+                         : HTTP_SERVICE_UNAVAILABLE);
+    }
+    
+    if (req_engine_done && ctx->engine) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, r->connection, 
+                      APLOGNO(03370)
+                      "h2_proxy_session(%s): finished request %s",
+                      ctx->engine_id, task_id);
+        req_engine_done(ctx->engine, r->connection, status);
+    }
 }    
 
+static void session_req_done(h2_proxy_session *session, request_rec *r,
+                             apr_status_t status, int touched)
+{
+    request_done(session->user_data, r, status, touched);
+}
+
 static apr_status_t next_request(h2_proxy_ctx *ctx, int before_leave)
 {
-    if (ctx->next) {
+    if (h2_proxy_fifo_count(ctx->requests) > 0) {
         return APR_SUCCESS;
     }
     else if (req_engine_pull && ctx->engine) {
         apr_status_t status;
+        request_rec *r = NULL;
+        
         status = req_engine_pull(ctx->engine, before_leave? 
                                  APR_BLOCK_READ: APR_NONBLOCK_READ, 
-                                 ctx->capacity, &ctx->next);
-        ap_log_cerror(APLOG_MARK, APLOG_TRACE2, status, ctx->owner, 
-                      "h2_proxy_engine(%s): pulled request (%s) %s", 
-                      ctx->engine_id, 
-                      before_leave? "before leave" : "regular", 
-                      (ctx->next? ctx->next->the_request : "NULL"));
+                                 ctx->capacity, &r);
+        if (status == APR_SUCCESS && r) {
+            ap_log_cerror(APLOG_MARK, APLOG_TRACE3, status, ctx->owner, 
+                          "h2_proxy_engine(%s): pulled request (%s) %s", 
+                          ctx->engine_id, 
+                          before_leave? "before leave" : "regular", 
+                          r->the_request);
+            h2_proxy_fifo_push(ctx->requests, r);
+        }
         return APR_STATUS_IS_EAGAIN(status)? APR_SUCCESS : status;
     }
     return APR_EOF;
@@ -335,15 +334,19 @@ static apr_status_t next_request(h2_prox
 
 static apr_status_t proxy_engine_run(h2_proxy_ctx *ctx) {
     apr_status_t status = OK;
+    int h2_front;
+    request_rec *r;
     
     /* Step Four: Send the Request in a new HTTP/2 stream and
      * loop until we got the response or encounter errors.
      */
     ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, ctx->owner, 
                   "eng(%s): setup session", ctx->engine_id);
-    ctx->session = h2_proxy_session_setup(ctx->engine_id, ctx->p_conn, ctx->conf, 
-                                          30, h2_log2(ctx->req_buffer_size), 
-                                          request_done);
+    h2_front = is_h2? is_h2(ctx->owner) : 0;
+    ctx->session = h2_proxy_session_setup(ctx->engine_id, ctx->p_conn, ctx->conf,
+                                          h2_front, 30, 
+                                          h2_proxy_log2((int)ctx->req_buffer_size), 
+                                          session_req_done);
     if (!ctx->session) {
         ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, 
                       APLOGNO(03372) "session unavailable");
@@ -354,10 +357,9 @@ static apr_status_t proxy_engine_run(h2_
                   "eng(%s): run session %s", ctx->engine_id, ctx->session->id);
     ctx->session->user_data = ctx;
     
-    while (1) {
-        if (ctx->next) {
-            add_request(ctx->session, ctx->next);
-            ctx->next = NULL;
+    while (!ctx->owner->aborted) {
+        if (APR_SUCCESS == h2_proxy_fifo_try_pull(ctx->requests, (void**)&r)) {
+            add_request(ctx->session, r);
         }
         
         status = h2_proxy_session_process(ctx->session);
@@ -367,7 +369,8 @@ static apr_status_t proxy_engine_run(h2_
             /* ongoing processing, call again */
             if (ctx->session->remote_max_concurrent > 0
                 && ctx->session->remote_max_concurrent != ctx->capacity) {
-                ctx->capacity = ctx->session->remote_max_concurrent;
+                ctx->capacity = H2MIN((int)ctx->session->remote_max_concurrent, 
+                                      h2_proxy_fifo_capacity(ctx->requests));
             }
             s2 = next_request(ctx, 0);
             if (s2 == APR_ECONNABORTED) {
@@ -375,10 +378,16 @@ static apr_status_t proxy_engine_run(h2_
                 ap_log_cerror(APLOG_MARK, APLOG_DEBUG, s2, ctx->owner, 
                               APLOGNO(03374) "eng(%s): pull request", 
                               ctx->engine_id);
-                status = s2;
+                /* give notice that we're leaving and cancel all ongoing
+                 * streams. */
+                next_request(ctx, 1); 
+                h2_proxy_session_cancel_all(ctx->session);
+                h2_proxy_session_process(ctx->session);
+                status = ctx->r_status = APR_SUCCESS;
                 break;
             }
-            if (!ctx->next && h2_ihash_empty(ctx->session->streams)) {
+            if ((h2_proxy_fifo_count(ctx->requests) == 0) 
+                && h2_proxy_ihash_empty(ctx->session->streams)) {
                 break;
             }
         }
@@ -392,7 +401,7 @@ static apr_status_t proxy_engine_run(h2_
              * a) be reopened on the new session iff safe to do so
              * b) reported as done (failed) otherwise
              */
-            h2_proxy_session_cleanup(ctx->session, request_done);
+            h2_proxy_session_cleanup(ctx->session, session_req_done);
             break;
         }
     }
@@ -403,7 +412,7 @@ static apr_status_t proxy_engine_run(h2_
     return status;
 }
 
-static h2_proxy_ctx *push_request_somewhere(h2_proxy_ctx *ctx)
+static apr_status_t push_request_somewhere(h2_proxy_ctx *ctx, request_rec *r)
 {
     conn_rec *c = ctx->owner;
     const char *engine_type, *hostname;
@@ -413,21 +422,15 @@ static h2_proxy_ctx *push_request_somewh
     engine_type = apr_psprintf(ctx->pool, "proxy_http2 %s%s", hostname, 
                                ctx->server_portstr);
     
-    if (c->master && req_engine_push && ctx->next && is_h2 && is_h2(c)) {
+    if (c->master && req_engine_push && r && is_h2 && is_h2(c)) {
         /* If we are have req_engine capabilities, push the handling of this
          * request (e.g. slave connection) to a proxy_http2 engine which 
          * uses the same backend. We may be called to create an engine 
          * ourself. */
-        if (req_engine_push(engine_type, ctx->next, proxy_engine_init)
-            == APR_SUCCESS) {
-            /* to renew the lifetime, we might have set a new ctx */
-            ctx = ap_get_module_config(c->conn_config, &proxy_http2_module);
+        if (req_engine_push(engine_type, r, proxy_engine_init) == APR_SUCCESS) {
             if (ctx->engine == NULL) {
-                /* Another engine instance has taken over processing of this
-                 * request. */
-                ctx->r_status = SUSPENDED;
-                ctx->next = NULL;
-                return ctx;
+                /* request has been assigned to an engine in another thread */
+                return SUSPENDED;
             }
         }
     }
@@ -448,7 +451,8 @@ static h2_proxy_ctx *push_request_somewh
         ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, c, 
                       "H2: hosting engine %s", ctx->engine_id);
     }
-    return ctx;
+
+    return h2_proxy_fifo_push(ctx->requests, r);
 }
 
 static int proxy_http2_handler(request_rec *r, 
@@ -465,7 +469,7 @@ static int proxy_http2_handler(request_r
     apr_status_t status;
     h2_proxy_ctx *ctx;
     apr_uri_t uri;
-    int reconnected = 0;
+    int reconnects = 0;
     
     /* find the scheme */
     if ((url[0] != 'h' && url[0] != 'H') || url[1] != '2') {
@@ -490,6 +494,7 @@ static int proxy_http2_handler(request_r
         default:
             return DECLINED;
     }
+    
     ctx = apr_pcalloc(r->pool, sizeof(*ctx));
     ctx->owner      = r->connection;
     ctx->pool       = r->pool;
@@ -501,8 +506,9 @@ static int proxy_http2_handler(request_r
     ctx->conf       = conf;
     ctx->flushall   = apr_table_get(r->subprocess_env, "proxy-flushall")? 1 : 0;
     ctx->r_status   = HTTP_SERVICE_UNAVAILABLE;
-    ctx->next       = r;
-    r = NULL;
+    
+    h2_proxy_fifo_set_create(&ctx->requests, ctx->pool, 100);
+    
     ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, ctx);
 
     /* scheme says, this is for us. */
@@ -548,10 +554,11 @@ run_connect:
     
     /* If we are not already hosting an engine, try to push the request 
      * to an already existing engine or host a new engine here. */
-    if (!ctx->engine) {
-        ctx = push_request_somewhere(ctx);
+    if (r && !ctx->engine) {
+        ctx->r_status = push_request_somewhere(ctx, r);
+        r = NULL;
         if (ctx->r_status == SUSPENDED) {
-            /* request was pushed to another engine */
+            /* request was pushed to another thread, leave processing here */
             goto cleanup;
         }
     }
@@ -561,37 +568,39 @@ run_connect:
      * backend->hostname. */
     if (ap_proxy_connect_backend(ctx->proxy_func, ctx->p_conn, ctx->worker, 
                                  ctx->server)) {
-        ap_log_cerror(APLOG_MARK, APLOG_ERR, 0, ctx->owner, APLOGNO(03352)
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(03352)
                       "H2: failed to make connection to backend: %s",
                       ctx->p_conn->hostname);
-        goto cleanup;
+        goto reconnect;
     }
     
     /* Step Three: Create conn_rec for the socket we have open now. */
     if (!ctx->p_conn->connection) {
-        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, APLOGNO(03353)
-                      "setup new connection: is_ssl=%d %s %s %s", 
-                      ctx->p_conn->is_ssl, ctx->p_conn->ssl_hostname, 
-                      locurl, ctx->p_conn->hostname);
         if ((status = ap_proxy_connection_create(ctx->proxy_func, ctx->p_conn,
                                                  ctx->owner, 
                                                  ctx->server)) != OK) {
-            goto cleanup;
+            ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, APLOGNO(03353)
+                          "setup new connection: is_ssl=%d %s %s %s", 
+                          ctx->p_conn->is_ssl, ctx->p_conn->ssl_hostname, 
+                          locurl, ctx->p_conn->hostname);
+            goto reconnect;
         }
         
-        /*
-         * On SSL connections set a note on the connection what CN is
-         * requested, such that mod_ssl can check if it is requested to do
-         * so.
-         */
-        if (ctx->p_conn->ssl_hostname) {
-            apr_table_setn(ctx->p_conn->connection->notes,
-                           "proxy-request-hostname", ctx->p_conn->ssl_hostname);
-        }
-        
-        if (ctx->is_ssl) {
-            apr_table_setn(ctx->p_conn->connection->notes,
-                           "proxy-request-alpn-protos", "h2");
+        if (!ctx->p_conn->data) {
+            /* New conection: set a note on the connection what CN is
+             * requested and what protocol we want */
+            if (ctx->p_conn->ssl_hostname) {
+                ap_log_cerror(APLOG_MARK, APLOG_TRACE1, status, ctx->owner, 
+                              "set SNI to %s for (%s)", 
+                              ctx->p_conn->ssl_hostname, 
+                              ctx->p_conn->hostname);
+                apr_table_setn(ctx->p_conn->connection->notes,
+                               "proxy-request-hostname", ctx->p_conn->ssl_hostname);
+            }
+            if (ctx->is_ssl) {
+                apr_table_setn(ctx->p_conn->connection->notes,
+                               "proxy-request-alpn-protos", "h2");
+            }
         }
     }
 
@@ -609,8 +618,8 @@ run_session:
         ctx->engine = NULL;
     }
 
-cleanup:
-    if (!reconnected && ctx->engine && next_request(ctx, 1) == APR_SUCCESS) {
+reconnect:
+    if (next_request(ctx, 1) == APR_SUCCESS) {
         /* Still more to do, tear down old conn and start over */
         if (ctx->p_conn) {
             ctx->p_conn->close = 1;
@@ -619,10 +628,16 @@ cleanup:
             ap_proxy_release_connection(ctx->proxy_func, ctx->p_conn, ctx->server);
             ctx->p_conn = NULL;
         }
-        reconnected = 1; /* we do this only once, then fail */
-        goto run_connect;
+        ++reconnects;
+        if (reconnects < 5 && !ctx->owner->aborted) {
+            goto run_connect;
+        } 
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, ctx->owner, APLOGNO(10023)
+                      "giving up after %d reconnects, %d requests todo",
+                      reconnects, h2_proxy_fifo_count(ctx->requests));
     }
     
+cleanup:
     if (ctx->p_conn) {
         if (status != APR_SUCCESS) {
             /* close socket when errors happened or session shut down (EOF) */
@@ -634,6 +649,11 @@ cleanup:
         ctx->p_conn = NULL;
     }
 
+    /* Any requests will still have need to fail */
+    while (APR_SUCCESS == h2_proxy_fifo_try_pull(ctx->requests, (void**)&r)) {
+        request_done(ctx, r, HTTP_SERVICE_UNAVAILABLE, 1);
+    }
+    
     ap_set_module_config(ctx->owner->conn_config, &proxy_http2_module, NULL);
     ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner, 
                   APLOGNO(03377) "leaving handler");
diff -up --new-file httpd-2.4.23/modules/http2/NWGNUmod_http2 /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/NWGNUmod_http2
--- httpd-2.4.23/modules/http2/NWGNUmod_http2	2016-06-28 21:57:30.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/NWGNUmod_http2	2017-03-31 21:41:01.000000000 +0200
@@ -186,7 +186,6 @@ TARGET_lib = \
 FILES_nlm_objs = \
 	$(OBJDIR)/h2_alt_svc.o \
 	$(OBJDIR)/h2_bucket_beam.o \
-	$(OBJDIR)/h2_bucket_eoc.o \
 	$(OBJDIR)/h2_bucket_eos.o \
 	$(OBJDIR)/h2_config.o \
 	$(OBJDIR)/h2_conn.o \
@@ -199,13 +198,12 @@ FILES_nlm_objs = \
 	$(OBJDIR)/h2_ngn_shed.o \
 	$(OBJDIR)/h2_push.o \
 	$(OBJDIR)/h2_request.o \
-	$(OBJDIR)/h2_response.o \
+	$(OBJDIR)/h2_headers.o \
 	$(OBJDIR)/h2_session.o \
 	$(OBJDIR)/h2_stream.o \
 	$(OBJDIR)/h2_switch.o \
 	$(OBJDIR)/h2_task.o \
 	$(OBJDIR)/h2_util.o \
-	$(OBJDIR)/h2_worker.o \
 	$(OBJDIR)/h2_workers.o \
 	$(OBJDIR)/mod_http2.o \
 	$(EOLIST)
@@ -355,25 +353,6 @@ $(OBJDIR)/mod_http2.imp : NWGNUmod_http2
 	@echo $(DL)GEN  $@$(DL)
 	@echo $(DL) (HTTP2)$(DL) > $@
 	@echo $(DL) http2_module,$(DL) >> $@
-	@echo $(DL) h2_ihash_add,$(DL) >> $@
-	@echo $(DL) h2_ihash_clear,$(DL) >> $@
-	@echo $(DL) h2_ihash_count,$(DL) >> $@
-	@echo $(DL) h2_ihash_create,$(DL) >> $@
-	@echo $(DL) h2_ihash_empty,$(DL) >> $@
-	@echo $(DL) h2_ihash_iter,$(DL) >> $@
-	@echo $(DL) h2_ihash_remove,$(DL) >> $@
-	@echo $(DL) h2_iq_add,$(DL) >> $@
-	@echo $(DL) h2_iq_create,$(DL) >> $@
-	@echo $(DL) h2_iq_remove,$(DL) >> $@
-	@echo $(DL) h2_log2,$(DL) >> $@
-	@echo $(DL) h2_proxy_res_ignore_header,$(DL) >> $@
-	@echo $(DL) h2_headers_add_h1,$(DL) >> $@
-	@echo $(DL) h2_req_create,$(DL) >> $@
-	@echo $(DL) h2_req_createn,$(DL) >> $@
-	@echo $(DL) h2_req_make,$(DL) >> $@
-	@echo $(DL) h2_util_camel_case_header,$(DL) >> $@
-	@echo $(DL) h2_util_frame_print,$(DL) >> $@
-	@echo $(DL) h2_util_ngheader_make_req,$(DL) >> $@
 	@echo $(DL) nghttp2_is_fatal,$(DL) >> $@
 	@echo $(DL) nghttp2_option_del,$(DL) >> $@
 	@echo $(DL) nghttp2_option_new,$(DL) >> $@
@@ -400,6 +379,7 @@ $(OBJDIR)/mod_http2.imp : NWGNUmod_http2
 	@echo $(DL) nghttp2_session_want_write,$(DL) >> $@
 	@echo $(DL) nghttp2_strerror,$(DL) >> $@
 	@echo $(DL) nghttp2_submit_goaway,$(DL) >> $@
+	@echo $(DL) nghttp2_submit_ping,$(DL) >> $@
 	@echo $(DL) nghttp2_submit_request,$(DL) >> $@
 	@echo $(DL) nghttp2_submit_rst_stream,$(DL) >> $@
 	@echo $(DL) nghttp2_submit_settings,$(DL) >> $@
diff -up --new-file httpd-2.4.23/modules/http2/NWGNUproxyht2 /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/NWGNUproxyht2
--- httpd-2.4.23/modules/http2/NWGNUproxyht2	2016-06-28 21:57:30.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/NWGNUproxyht2	2016-08-31 11:14:39.000000000 +0200
@@ -182,6 +182,7 @@ TARGET_lib = \
 FILES_nlm_objs = \
 	$(OBJDIR)/mod_proxy_http2.o \
 	$(OBJDIR)/h2_proxy_session.o \
+	$(OBJDIR)/h2_proxy_util.o \
 	$(EOLIST)
 
 #
diff -up --new-file httpd-2.4.23/modules/http2/README.h2 /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/README.h2
--- httpd-2.4.23/modules/http2/README.h2	2015-09-28 21:30:00.000000000 +0200
+++ /home/pgajdos/osc/Apache/apache2/httpd-2.4.29/modules/http2/README.h2	2016-08-25 14:48:18.000000000 +0200
@@ -60,7 +60,7 @@ TLS CONFIGURATION
 
 If you want to use HTTP/2 with a browser, most modern browsers will support
 it without further configuration. However, browsers so far only support
-HTTP/2 over TLS and are expecially picky about the certificate and
+HTTP/2 over TLS and are especially picky about the certificate and
 encryption ciphers used.
 
 Server admins may look for up-to-date information about "modern" TLS
