You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2021/07/06 13:06:00 UTC

svn commit: r1891312 - in /httpd/httpd/trunk: changes-entries/ modules/http2/

Author: icing
Date: Tue Jul  6 13:06:00 2021
New Revision: 1891312

URL: http://svn.apache.org/viewvc?rev=1891312&view=rev
Log:
  *) mod_http2:
   - Aborting requests via RST_STREAM no longer affect the available
     resources of a connection when the first chunk of the response
     body has been sent.
   - H2Min/MaxWorkers behave as intended again. The module will initially
     create H2MinWorkers threads and add up to H2MaxWorkers when needed. These
     additional workers time out when idle after H2MaxWorkerIdleSeconds and
     disappear again.
   - When the shutdown of a child is detected (e.g. graceful shutdown), the
     module will terminate all idle workers above H2MinWorkers right away.
     This detection currently only happens when a HTTP/2 connection is active.


Added:
    httpd/httpd/trunk/changes-entries/h2_workers_dynamic.txt
Modified:
    httpd/httpd/trunk/modules/http2/h2_mplx.c
    httpd/httpd/trunk/modules/http2/h2_request.c
    httpd/httpd/trunk/modules/http2/h2_session.c
    httpd/httpd/trunk/modules/http2/h2_task.c
    httpd/httpd/trunk/modules/http2/h2_version.h
    httpd/httpd/trunk/modules/http2/h2_workers.c
    httpd/httpd/trunk/modules/http2/h2_workers.h
    httpd/httpd/trunk/modules/http2/mod_proxy_http2.c

Added: httpd/httpd/trunk/changes-entries/h2_workers_dynamic.txt
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/changes-entries/h2_workers_dynamic.txt?rev=1891312&view=auto
==============================================================================
--- httpd/httpd/trunk/changes-entries/h2_workers_dynamic.txt (added)
+++ httpd/httpd/trunk/changes-entries/h2_workers_dynamic.txt Tue Jul  6 13:06:00 2021
@@ -0,0 +1,13 @@
+  *) mod_http2:
+   - Aborting requests via RST_STREAM no longer affect the available
+     resources of a connection when the first chunk of the response
+     body has been sent.
+   - H2Min/MaxWorkers behave as intended again. The module will initially
+     create H2MinWorkers threads and add up to H2MaxWorkers when needed. These
+     additional workers time out when idle after H2MaxWorkerIdleSeconds and
+     disappear again.
+   - When the shutdown of a child is detected (e.g. graceful shutdown), the
+     module will terminate all idle workers above H2MinWorkers right away.
+     This detection currently only happens when a HTTP/2 connection is active.
+     [Stefan Eissing]
+

Modified: httpd/httpd/trunk/modules/http2/h2_mplx.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_mplx.c?rev=1891312&r1=1891311&r2=1891312&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_mplx.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_mplx.c Tue Jul  6 13:06:00 2021
@@ -739,6 +739,12 @@ static h2_task *s_next_stream_task(h2_mp
             return stream->task;
         }
     }
+    if (m->tasks_active >= m->limit_active && !h2_iq_empty(m->q)) {
+        ap_log_cerror(APLOG_MARK, APLOG_DEBUG, 0, m->c,
+                      "h2_session(%ld): delaying request processing. "
+                      "Current limit is %d and %d workers are in use.",
+                      m->id, m->limit_active, m->tasks_active);
+    }
     return NULL;
 }
 
@@ -1132,14 +1138,36 @@ int h2_mplx_m_awaits_data(h2_mplx *m)
     return waiting;
 }
 
+static int reset_is_acceptable(h2_stream *stream)
+{
+    /* client may terminate a stream via H2 RST_STREAM message at any time.
+     * This is annyoing when we have committed resources (e.g. worker threads)
+     * to it, so our mood (e.g. willingness to commit resources on this
+     * connection in the future) goes down.
+     *
+     * This is a DoS protection. We do not want to make it too easy for
+     * a client to eat up server resources.
+     *
+     * However: there are cases where a RST_STREAM is the only way to end
+     * a request. This includes websockets and server-side-event streams (SSEs).
+     * The responses to such requests continue forever otherwise.
+     *
+     */
+    if (!stream->task) return 1; /* have not started or already ended for us. acceptable. */
+    if (!(stream->id & 0x01)) return 1; /* stream initiated by us. acceptable. */
+    if (!stream->has_response) return 0; /* no response headers produced yet. bad. */
+    if (!stream->out_data_frames) return 0; /* no response body data sent yet. bad. */
+    return 1; /* otherwise, be forgiving */
+}
+
 apr_status_t h2_mplx_m_client_rst(h2_mplx *m, int stream_id)
 {
     h2_stream *stream;
     apr_status_t status = APR_SUCCESS;
-    
+
     H2_MPLX_ENTER_ALWAYS(m);
     stream = h2_ihash_get(m->streams, stream_id);
-    if (stream && stream->task) {
+    if (stream && !reset_is_acceptable(stream)) {
         status = m_be_annoyed(m);
     }
     H2_MPLX_LEAVE(m);

Modified: httpd/httpd/trunk/modules/http2/h2_request.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_request.c?rev=1891312&r1=1891311&r2=1891312&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_request.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_request.c Tue Jul  6 13:06:00 2021
@@ -210,12 +210,74 @@ h2_request *h2_request_clone(apr_pool_t
     return dst;
 }
 
+#if !AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
+static request_rec *my_ap_create_request(conn_rec *c)
+{
+    apr_pool_t *p;
+    request_rec *r;
+
+    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)c);
+    r->pool            = p;
+    r->connection      = c;
+    r->server          = c->base_server;
+
+    r->user            = NULL;
+    r->ap_auth_type    = NULL;
+
+    r->allowed_methods = ap_make_method_list(p, 2);
+
+    r->headers_in      = apr_table_make(r->pool, 5);
+    r->trailers_in     = apr_table_make(r->pool, 5);
+    r->subprocess_env  = apr_table_make(r->pool, 25);
+    r->headers_out     = apr_table_make(r->pool, 12);
+    r->err_headers_out = apr_table_make(r->pool, 5);
+    r->trailers_out    = apr_table_make(r->pool, 5);
+    r->notes           = apr_table_make(r->pool, 5);
+
+    r->request_config  = ap_create_request_config(r->pool);
+    /* Must be set before we run create request hook */
+
+    r->proto_output_filters = c->output_filters;
+    r->output_filters  = r->proto_output_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;
+
+    r->sent_bodyct     = 0;                      /* bytect isn't for body */
+
+    r->read_length     = 0;
+    r->read_body       = REQUEST_NO_BODY;
+
+    r->status          = HTTP_OK;  /* Until further notice */
+    r->header_only     = 0;
+    r->the_request     = NULL;
+
+    /* Begin by presuming any module can make its own path_info assumptions,
+     * until some module interjects and changes the value.
+     */
+    r->used_path_info = AP_REQ_DEFAULT_PATH_INFO;
+
+    r->useragent_addr = c->client_addr;
+    r->useragent_ip = c->client_ip;
+    return r;
+}
+#endif
+
 request_rec *h2_request_create_rec(const h2_request *req, conn_rec *c)
 {
     int access_status = HTTP_OK;    
 
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 106)
     request_rec *r = ap_create_request(c);
+#else
+    request_rec *r = my_ap_create_request(c);
+#endif
 
+#if AP_MODULE_MAGIC_AT_LEAST(20120211, 107)
     ap_run_pre_read_request(r, c);
 
     /* Time to populate r with the data we have. */
@@ -244,6 +306,49 @@ request_rec *h2_request_create_rec(const
         r->status = HTTP_OK;
         goto die;
     }
+#else
+    {
+        const char *s;
+
+        r->headers_in = apr_table_clone(r->pool, req->headers);
+        ap_run_pre_read_request(r, c);
+
+        /* Time to populate r with the data we have. */
+        r->request_time = req->request_time;
+        r->method = apr_pstrdup(r->pool, req->method);
+        /* Provide quick information about the request method as soon as known */
+        r->method_number = ap_method_number_of(r->method);
+        if (r->method_number == M_GET && r->method[0] == 'H') {
+            r->header_only = 1;
+        }
+        ap_parse_uri(r, req->path ? req->path : "");
+        r->protocol = (char*)"HTTP/2.0";
+        r->proto_num = HTTP_VERSION(2, 0);
+        r->the_request = apr_psprintf(r->pool, "%s %s HTTP/2.0",
+                                      r->method, req->path ? req->path : "");
+
+        /* Start with r->hostname = NULL, ap_check_request_header() will get it
+         * form Host: header, otherwise we get complains about port numbers.
+         */
+        r->hostname = NULL;
+        ap_update_vhost_from_headers(r);
+
+         /* 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;
+                access_status = r->status;
+                goto die;
+            }
+         }
+    }
+#endif
 
     /* we may have switched to another server */
     r->per_dir_config = r->server->lookup_defaults;

Modified: httpd/httpd/trunk/modules/http2/h2_session.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_session.c?rev=1891312&r1=1891311&r2=1891312&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_session.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_session.c Tue Jul  6 13:06:00 2021
@@ -1908,6 +1908,7 @@ static void h2_session_ev_mpm_stopping(h
             break;
         default:
             h2_session_shutdown_notice(session);
+            h2_workers_graceful_shutdown(session->workers);
             break;
     }
 }

Modified: httpd/httpd/trunk/modules/http2/h2_task.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_task.c?rev=1891312&r1=1891311&r2=1891312&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_task.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_task.c Tue Jul  6 13:06:00 2021
@@ -593,7 +593,7 @@ apr_status_t h2_task_do(h2_task *task, a
          * configurations by mod_h2 alone. 
          */
         task->c->id = (c->master->id << 8)^worker_id;
-        task->id = apr_psprintf(task->pool, "%ld-%d", c->master->id, 
+        task->id = apr_psprintf(task->pool, "%ld-%d", task->mplx->id,
                                 task->stream_id);
     }
         

Modified: httpd/httpd/trunk/modules/http2/h2_version.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_version.h?rev=1891312&r1=1891311&r2=1891312&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_version.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_version.h Tue Jul  6 13:06:00 2021
@@ -27,7 +27,7 @@
  * @macro
  * Version number of the http2 module as c string
  */
-#define MOD_HTTP2_VERSION "1.15.18"
+#define MOD_HTTP2_VERSION "1.15.21"
 
 /**
  * @macro
@@ -35,7 +35,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 0x010f12
+#define MOD_HTTP2_VERSION_NUM 0x010f15
 
 
 #endif /* mod_h2_h2_version_h */

Modified: httpd/httpd/trunk/modules/http2/h2_workers.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_workers.c?rev=1891312&r1=1891311&r2=1891312&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_workers.c (original)
+++ httpd/httpd/trunk/modules/http2/h2_workers.c Tue Jul  6 13:06:00 2021
@@ -41,6 +41,7 @@ struct h2_slot {
     apr_thread_t *thread;
     apr_thread_mutex_t *lock;
     apr_thread_cond_t *not_idle;
+    volatile apr_uint32_t timed_out;
 };
 
 static h2_slot *pop_slot(h2_slot *volatile *phead) 
@@ -71,47 +72,47 @@ static void push_slot(h2_slot *volatile
 }
 
 static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx);
+static void slot_done(h2_slot *slot);
 
 static apr_status_t activate_slot(h2_workers *workers, h2_slot *slot) 
 {
-    apr_status_t status;
+    apr_status_t rv;
     
     slot->workers = workers;
     slot->task = NULL;
 
+    apr_thread_mutex_lock(workers->lock);
     if (!slot->lock) {
-        status = apr_thread_mutex_create(&slot->lock,
+        rv = apr_thread_mutex_create(&slot->lock,
                                          APR_THREAD_MUTEX_DEFAULT,
                                          workers->pool);
-        if (status != APR_SUCCESS) {
-            push_slot(&workers->free, slot);
-            return status;
-        }
+        if (rv != APR_SUCCESS) goto cleanup;
     }
 
     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;
-        }
+        rv = apr_thread_cond_create(&slot->not_idle, workers->pool);
+        if (rv != APR_SUCCESS) goto cleanup;
     }
     
-    ap_log_error(APLOG_MARK, APLOG_TRACE2, 0, workers->s,
+    ap_log_error(APLOG_MARK, APLOG_WARNING, 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_atomic_inc32(&workers->worker_count);
-    status = apr_thread_create(&slot->thread, workers->thread_attr,
+    slot->timed_out = 0;
+    rv = apr_thread_create(&slot->thread, workers->thread_attr,
                                slot_run, slot, workers->pool);
-    if (status != APR_SUCCESS) {
+    if (rv != APR_SUCCESS) {
         apr_atomic_dec32(&workers->worker_count);
+    }
+
+cleanup:
+    apr_thread_mutex_unlock(workers->lock);
+    if (rv != APR_SUCCESS) {
         push_slot(&workers->free, slot);
-        return status;
     }
-    
-    return APR_SUCCESS;
+    return rv;
 }
 
 static apr_status_t add_worker(h2_workers *workers)
@@ -127,11 +128,19 @@ static void wake_idle_worker(h2_workers
 {
     h2_slot *slot = pop_slot(&workers->idle);
     if (slot) {
+        int timed_out = 0;
         apr_thread_mutex_lock(slot->lock);
-        apr_thread_cond_signal(slot->not_idle);
+        timed_out = slot->timed_out;
+        if (!timed_out) {
+            apr_thread_cond_signal(slot->not_idle);
+        }
         apr_thread_mutex_unlock(slot->lock);
+        if (timed_out) {
+            slot_done(slot);
+            wake_idle_worker(workers);
+        }
     }
-    else if (workers->dynamic) {
+    else if (workers->dynamic && !workers->shutdown) {
         add_worker(workers);
     }
 }
@@ -185,13 +194,18 @@ static h2_fifo_op_t mplx_peek(void *head
 static int get_next(h2_slot *slot)
 {
     h2_workers *workers = slot->workers;
+    int non_essential = slot->id >= workers->min_workers;
+    apr_status_t rv;
 
-    while (!workers->aborted) {
+    while (!workers->aborted && !slot->timed_out) {
         ap_assert(slot->task == NULL);
+        if (non_essential && workers->shutdown) {
+            /* Terminate non-essential worker on shutdown */
+            break;
+        }
         if (h2_fifo_try_peek(workers->mplxs, mplx_peek, slot) == APR_EOF) {
             /* The queue is terminated with the MPM child being cleaned up,
-             * just leave.
-             */
+             * just leave. */
             break;
         }
         if (slot->task) {
@@ -202,8 +216,18 @@ static int get_next(h2_slot *slot)
 
         apr_thread_mutex_lock(slot->lock);
         if (!workers->aborted) {
+
             push_slot(&workers->idle, slot);
-            apr_thread_cond_wait(slot->not_idle, slot->lock);
+            if (non_essential && workers->max_idle_duration) {
+                rv = apr_thread_cond_timedwait(slot->not_idle, slot->lock,
+                                               workers->max_idle_duration);
+                if (APR_TIMEUP == rv) {
+                    slot->timed_out = 1;
+                }
+            }
+            else {
+                apr_thread_cond_wait(slot->not_idle, slot->lock);
+            }
         }
         apr_thread_mutex_unlock(slot->lock);
     }
@@ -251,17 +275,36 @@ static void* APR_THREAD_FUNC slot_run(ap
         } while (slot->task);
     }
 
-    slot_done(slot);
+    if (!slot->timed_out) {
+        slot_done(slot);
+    }
 
     apr_thread_exit(thread, APR_SUCCESS);
     return NULL;
 }
 
-static apr_status_t workers_pool_cleanup(void *data)
+static void wake_non_essential_workers(h2_workers *workers)
 {
-    h2_workers *workers = data;
     h2_slot *slot;
-    
+    /* pop all idle, signal the non essentials and add the others again */
+    if ((slot = pop_slot(&workers->idle))) {
+        wake_non_essential_workers(workers);
+        if (slot->id > workers->min_workers) {
+            apr_thread_mutex_lock(slot->lock);
+            apr_thread_cond_signal(slot->not_idle);
+            apr_thread_mutex_unlock(slot->lock);
+        }
+        else {
+            push_slot(&workers->idle, slot);
+        }
+    }
+}
+
+static void workers_abort_idle(h2_workers *workers)
+{
+    h2_slot *slot;
+
+    workers->shutdown = 1;
     workers->aborted = 1;
     h2_fifo_term(workers->mplxs);
 
@@ -271,6 +314,13 @@ static apr_status_t workers_pool_cleanup
         apr_thread_cond_signal(slot->not_idle);
         apr_thread_mutex_unlock(slot->lock);
     }
+}
+
+static apr_status_t workers_pool_cleanup(void *data)
+{
+    h2_workers *workers = data;
+
+    workers_abort_idle(workers);
 
     /* wait for all the workers to become zombies and join them */
     apr_thread_mutex_lock(workers->lock);
@@ -287,7 +337,7 @@ h2_workers *h2_workers_create(server_rec
                               int min_workers, int max_workers,
                               int idle_secs)
 {
-    apr_status_t status;
+    apr_status_t rv;
     h2_workers *workers;
     apr_pool_t *pool;
     int i, n;
@@ -311,7 +361,7 @@ h2_workers *h2_workers_create(server_rec
     workers->pool = pool;
     workers->min_workers = min_workers;
     workers->max_workers = max_workers;
-    workers->max_idle_secs = (idle_secs > 0)? idle_secs : 10;
+    workers->max_idle_duration = apr_time_from_sec((idle_secs > 0)? idle_secs : 10);
 
     /* FIXME: the fifo set we use here has limited capacity. Once the
      * set is full, connections with new requests do a wait. Unfortunately,
@@ -324,16 +374,12 @@ h2_workers *h2_workers_create(server_rec
      * For now, we just make enough room to have many connections inside one
      * process.
      */
-    status = h2_fifo_set_create(&workers->mplxs, pool, 8 * 1024);
-    if (status != APR_SUCCESS) {
-        return NULL;
-    }
-    
-    status = apr_threadattr_create(&workers->thread_attr, workers->pool);
-    if (status != APR_SUCCESS) {
-        return NULL;
-    }
-    
+    rv = h2_fifo_set_create(&workers->mplxs, pool, 8 * 1024);
+    if (rv != APR_SUCCESS) goto cleanup;
+
+    rv = apr_threadattr_create(&workers->thread_attr, workers->pool);
+    if (rv != APR_SUCCESS) goto cleanup;
+
     if (ap_thread_stacksize != 0) {
         apr_threadattr_stacksize_set(workers->thread_attr,
                                      ap_thread_stacksize);
@@ -342,38 +388,39 @@ h2_workers *h2_workers_create(server_rec
                      (long)ap_thread_stacksize);
     }
     
-    status = apr_thread_mutex_create(&workers->lock,
-                                     APR_THREAD_MUTEX_DEFAULT,
-                                     workers->pool);
-    if (status == APR_SUCCESS) {        
-        status = apr_thread_cond_create(&workers->all_done, 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) {
-            n = workers->nslots = 0;
-            status = APR_ENOMEM;
-        }
-        for (i = 0; i < n; ++i) {
-            workers->slots[i].id = i;
-        }
-    }
-    if (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]);
-        }
-        workers->dynamic = (workers->worker_count < workers->max_workers);
+    rv = apr_thread_mutex_create(&workers->lock,
+                                 APR_THREAD_MUTEX_DEFAULT,
+                                 workers->pool);
+    if (rv != APR_SUCCESS) goto cleanup;
+    rv = apr_thread_cond_create(&workers->all_done, workers->pool);
+    if (rv != APR_SUCCESS) goto cleanup;
+
+    n = workers->nslots = workers->max_workers;
+    workers->slots = apr_pcalloc(workers->pool, n * sizeof(h2_slot));
+    if (workers->slots == NULL) {
+        n = workers->nslots = 0;
+        rv = APR_ENOMEM;
+        goto cleanup;
+    }
+    for (i = 0; i < n; ++i) {
+        workers->slots[i].id = i;
+    }
+    /* 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->min_workers;
+    for (i = n-1; i >= 0; --i) {
+        rv = activate_slot(workers, &workers->slots[i]);
+        if (rv != APR_SUCCESS) goto cleanup;
+    }
+    /* the rest of the slots go on the free list */
+    for(i = n; i < workers->nslots; ++i) {
+        push_slot(&workers->free, &workers->slots[i]);
     }
-    if (status == APR_SUCCESS) {
+    workers->dynamic = (workers->worker_count < workers->max_workers);
+
+cleanup:
+    if (rv == APR_SUCCESS) {
         /* Stop/join the workers threads when the MPM child exits (pchild is
          * destroyed), and as a pre_cleanup of pchild thus before the threads
          * pools (children of workers->pool) so that they are not destroyed
@@ -396,3 +443,10 @@ apr_status_t h2_workers_unregister(h2_wo
 {
     return h2_fifo_remove(workers->mplxs, m);
 }
+
+void h2_workers_graceful_shutdown(h2_workers *workers)
+{
+    workers->shutdown = 1;
+    h2_fifo_term(workers->mplxs);
+    wake_non_essential_workers(workers);
+}

Modified: httpd/httpd/trunk/modules/http2/h2_workers.h
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/h2_workers.h?rev=1891312&r1=1891311&r2=1891312&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/h2_workers.h (original)
+++ httpd/httpd/trunk/modules/http2/h2_workers.h Tue Jul  6 13:06:00 2021
@@ -40,9 +40,10 @@ struct h2_workers {
     int next_worker_id;
     apr_uint32_t min_workers;
     apr_uint32_t max_workers;
-    int max_idle_secs;
+    apr_interval_time_t max_idle_duration;
     
     volatile int aborted;
+    volatile int shutdown;
     int dynamic;
 
     apr_threadattr_t *thread_attr;
@@ -80,4 +81,9 @@ apr_status_t h2_workers_register(h2_work
  */
 apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m);
 
+/**
+ *  Shut down processing gracefully by terminating all idle workers.
+ */
+void h2_workers_graceful_shutdown(h2_workers *workers);
+
 #endif /* defined(__mod_h2__h2_workers__) */

Modified: httpd/httpd/trunk/modules/http2/mod_proxy_http2.c
URL: http://svn.apache.org/viewvc/httpd/httpd/trunk/modules/http2/mod_proxy_http2.c?rev=1891312&r1=1891311&r2=1891312&view=diff
==============================================================================
--- httpd/httpd/trunk/modules/http2/mod_proxy_http2.c (original)
+++ httpd/httpd/trunk/modules/http2/mod_proxy_http2.c Tue Jul  6 13:06:00 2021
@@ -397,18 +397,9 @@ run_connect:
     
     if (!ctx->p_conn->data && ctx->is_ssl) {
         /* New SSL connection: set a note on the connection about what
-         * protocol we want.
-         */
+         * protocol we need. */
         apr_table_setn(ctx->p_conn->connection->notes,
                        "proxy-request-alpn-protos", "h2");
-        if (ctx->p_conn->ssl_hostname) {
-            ap_log_cerror(APLOG_MARK, APLOG_TRACE1, 0, 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->master->aborted) goto cleanup;