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 2023/01/09 07:35:19 UTC
svn commit: r1906475 [10/11] - in /httpd/httpd/branches/2.4.x: ./ changes-entries/ modules/http2/ test/ test/modules/http2/ test/modules/http2/htdocs/cgi/ test/modules/http2/mod_h2test/ test/pyhttpd/ test/pyhttpd/mod_aptest/
Modified: httpd/httpd/branches/2.4.x/modules/http2/h2_workers.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/http2/h2_workers.c?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/http2/h2_workers.c (original)
+++ httpd/httpd/branches/2.4.x/modules/http2/h2_workers.c Mon Jan 9 07:35:18 2023
@@ -15,304 +15,352 @@
*/
#include <assert.h>
-#include <apr_atomic.h>
+#include <apr_ring.h>
#include <apr_thread_mutex.h>
#include <apr_thread_cond.h>
#include <mpm_common.h>
#include <httpd.h>
+#include <http_connection.h>
#include <http_core.h>
#include <http_log.h>
+#include <http_protocol.h>
#include "h2.h"
#include "h2_private.h"
#include "h2_mplx.h"
-#include "h2_task.h"
+#include "h2_c2.h"
#include "h2_workers.h"
#include "h2_util.h"
+typedef enum {
+ PROD_IDLE,
+ PROD_ACTIVE,
+ PROD_JOINED,
+} prod_state_t;
+
+struct ap_conn_producer_t {
+ APR_RING_ENTRY(ap_conn_producer_t) link;
+ const char *name;
+ void *baton;
+ ap_conn_producer_next *fn_next;
+ ap_conn_producer_done *fn_done;
+ ap_conn_producer_shutdown *fn_shutdown;
+ volatile prod_state_t state;
+ volatile int conns_active;
+};
+
+
+typedef enum {
+ H2_SLOT_FREE,
+ H2_SLOT_RUN,
+ H2_SLOT_ZOMBIE,
+} h2_slot_state_t;
+
typedef struct h2_slot h2_slot;
struct h2_slot {
- int id;
- int sticks;
- h2_slot *next;
+ APR_RING_ENTRY(h2_slot) link;
+ apr_uint32_t id;
+ apr_pool_t *pool;
+ h2_slot_state_t state;
+ volatile int should_shutdown;
+ volatile int is_idle;
h2_workers *workers;
- h2_task *task;
+ ap_conn_producer_t *prod;
apr_thread_t *thread;
- apr_thread_mutex_t *lock;
- apr_thread_cond_t *not_idle;
- volatile apr_uint32_t timed_out;
+ struct apr_thread_cond_t *more_work;
+ int activations;
};
-static h2_slot *pop_slot(h2_slot *volatile *phead)
-{
- /* 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;
- }
- }
-}
+struct h2_workers {
+ server_rec *s;
+ apr_pool_t *pool;
+
+ apr_uint32_t max_slots;
+ apr_uint32_t min_active;
+ volatile apr_time_t idle_limit;
+ volatile int aborted;
+ volatile int shutdown;
+ int dynamic;
+
+ volatile apr_uint32_t active_slots;
+ volatile apr_uint32_t idle_slots;
+
+ apr_threadattr_t *thread_attr;
+ h2_slot *slots;
+
+ APR_RING_HEAD(h2_slots_free, h2_slot) free;
+ APR_RING_HEAD(h2_slots_idle, h2_slot) idle;
+ APR_RING_HEAD(h2_slots_busy, h2_slot) busy;
+ APR_RING_HEAD(h2_slots_zombie, h2_slot) zombie;
+
+ APR_RING_HEAD(ap_conn_producer_active, ap_conn_producer_t) prod_active;
+ APR_RING_HEAD(ap_conn_producer_idle, ap_conn_producer_t) prod_idle;
+
+ struct apr_thread_mutex_t *lock;
+ struct apr_thread_cond_t *prod_done;
+ struct apr_thread_cond_t *all_done;
+};
-static void push_slot(h2_slot *volatile *phead, h2_slot *slot)
-{
- /* 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 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)
+static apr_status_t activate_slot(h2_workers *workers)
{
+ h2_slot *slot;
+ apr_pool_t *pool;
apr_status_t rv;
-
- slot->workers = workers;
- slot->task = NULL;
- apr_thread_mutex_lock(workers->lock);
- if (!slot->lock) {
- rv = apr_thread_mutex_create(&slot->lock,
- APR_THREAD_MUTEX_DEFAULT,
- workers->pool);
- if (rv != APR_SUCCESS) goto cleanup;
+ if (APR_RING_EMPTY(&workers->free, h2_slot, link)) {
+ return APR_EAGAIN;
}
+ slot = APR_RING_FIRST(&workers->free);
+ ap_assert(slot->state == H2_SLOT_FREE);
+ APR_RING_REMOVE(slot, link);
- if (!slot->not_idle) {
- rv = apr_thread_cond_create(&slot->not_idle, workers->pool);
- if (rv != APR_SUCCESS) goto cleanup;
- }
-
ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
- "h2_workers: new thread for slot %d", slot->id);
+ "h2_workers: activate slot %d", slot->id);
+
+ slot->state = H2_SLOT_RUN;
+ slot->should_shutdown = 0;
+ slot->is_idle = 0;
+ slot->pool = NULL;
+ ++workers->active_slots;
+ rv = apr_pool_create(&pool, workers->pool);
+ if (APR_SUCCESS != rv) goto cleanup;
+ apr_pool_tag(pool, "h2_worker_slot");
+ slot->pool = pool;
- /* thread will either immediately start work or add itself
- * to the idle queue */
- apr_atomic_inc32(&workers->worker_count);
- slot->timed_out = 0;
rv = ap_thread_create(&slot->thread, workers->thread_attr,
- slot_run, slot, workers->pool);
- if (rv != APR_SUCCESS) {
- apr_atomic_dec32(&workers->worker_count);
- }
+ slot_run, slot, slot->pool);
cleanup:
- apr_thread_mutex_unlock(workers->lock);
if (rv != APR_SUCCESS) {
- push_slot(&workers->free, slot);
- }
- return rv;
-}
-
-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) {
- int timed_out = 0;
- apr_thread_mutex_lock(slot->lock);
- timed_out = slot->timed_out;
- if (!timed_out) {
- apr_thread_cond_signal(slot->not_idle);
+ AP_DEBUG_ASSERT(0);
+ slot->state = H2_SLOT_FREE;
+ if (slot->pool) {
+ apr_pool_destroy(slot->pool);
+ slot->pool = NULL;
}
- apr_thread_mutex_unlock(slot->lock);
- if (timed_out) {
- slot_done(slot);
- wake_idle_worker(workers);
- }
- }
- else if (workers->dynamic && !workers->shutdown) {
- add_worker(workers);
+ APR_RING_INSERT_TAIL(&workers->free, slot, h2_slot, link);
+ --workers->active_slots;
}
+ return rv;
}
static void join_zombies(h2_workers *workers)
{
h2_slot *slot;
- while ((slot = pop_slot(&workers->zombies))) {
- apr_status_t status;
+ apr_status_t status;
+
+ while (!APR_RING_EMPTY(&workers->zombie, h2_slot, link)) {
+ slot = APR_RING_FIRST(&workers->zombie);
+ APR_RING_REMOVE(slot, link);
+ ap_assert(slot->state == H2_SLOT_ZOMBIE);
ap_assert(slot->thread != NULL);
+
+ apr_thread_mutex_unlock(workers->lock);
apr_thread_join(&status, slot->thread);
- slot->thread = NULL;
+ apr_thread_mutex_lock(workers->lock);
- push_slot(&workers->free, slot);
+ slot->thread = NULL;
+ slot->state = H2_SLOT_FREE;
+ if (slot->pool) {
+ apr_pool_destroy(slot->pool);
+ slot->pool = NULL;
+ }
+ APR_RING_INSERT_TAIL(&workers->free, slot, h2_slot, link);
}
}
-static apr_status_t slot_pull_task(h2_slot *slot, h2_mplx *m)
+static void wake_idle_worker(h2_workers *workers, ap_conn_producer_t *prod)
{
- apr_status_t rv;
-
- rv = h2_mplx_s_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;
+ if (!APR_RING_EMPTY(&workers->idle, h2_slot, link)) {
+ h2_slot *slot;
+ for (slot = APR_RING_FIRST(&workers->idle);
+ slot != APR_RING_SENTINEL(&workers->idle, h2_slot, link);
+ slot = APR_RING_NEXT(slot, link)) {
+ if (slot->is_idle && !slot->should_shutdown) {
+ apr_thread_cond_signal(slot->more_work);
+ slot->is_idle = 0;
+ return;
+ }
+ }
+ }
+ if (workers->dynamic && !workers->shutdown
+ && (workers->active_slots < workers->max_slots)) {
+ activate_slot(workers);
}
- 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.
+ * Get the next connection to work on.
*/
-static int get_next(h2_slot *slot)
+static conn_rec *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 && !slot->timed_out) {
- ap_assert(slot->task == NULL);
- if (non_essential && workers->shutdown) {
- /* Terminate non-essential worker on shutdown */
- break;
+ conn_rec *c = NULL;
+ ap_conn_producer_t *prod;
+ int has_more;
+
+ slot->prod = NULL;
+ if (!APR_RING_EMPTY(&workers->prod_active, ap_conn_producer_t, link)) {
+ slot->prod = prod = APR_RING_FIRST(&workers->prod_active);
+ APR_RING_REMOVE(prod, link);
+ AP_DEBUG_ASSERT(PROD_ACTIVE == prod->state);
+
+ c = prod->fn_next(prod->baton, &has_more);
+ if (c && has_more) {
+ APR_RING_INSERT_TAIL(&workers->prod_active, prod, ap_conn_producer_t, link);
+ wake_idle_worker(workers, slot->prod);
}
- 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. */
- break;
- }
- if (slot->task) {
- return 1;
+ else {
+ prod->state = PROD_IDLE;
+ APR_RING_INSERT_TAIL(&workers->prod_idle, prod, ap_conn_producer_t, link);
}
-
- join_zombies(workers);
-
- apr_thread_mutex_lock(slot->lock);
- if (!workers->aborted) {
-
- push_slot(&workers->idle, slot);
- 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);
- }
+ if (c) {
+ ++prod->conns_active;
}
- apr_thread_mutex_unlock(slot->lock);
}
- return 0;
+ return c;
}
-static void slot_done(h2_slot *slot)
+static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx)
{
+ h2_slot *slot = wctx;
h2_workers *workers = slot->workers;
+ conn_rec *c;
+ apr_status_t rv;
- push_slot(&workers->zombies, slot);
+ apr_thread_mutex_lock(workers->lock);
+ slot->state = H2_SLOT_RUN;
+ ++slot->activations;
+ APR_RING_ELEM_INIT(slot, link);
+ for(;;) {
+ if (APR_RING_NEXT(slot, link) != slot) {
+ /* slot is part of the idle ring from the last loop */
+ APR_RING_REMOVE(slot, link);
+ --workers->idle_slots;
+ }
+ slot->is_idle = 0;
+
+ if (!workers->aborted && !slot->should_shutdown) {
+ APR_RING_INSERT_TAIL(&workers->busy, slot, h2_slot, link);
+ do {
+ c = get_next(slot);
+ if (!c) {
+ break;
+ }
+ apr_thread_mutex_unlock(workers->lock);
+ /* See the discussion at <https://github.com/icing/mod_h2/issues/195>
+ *
+ * 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 secondary
+ * connections also.
+ *
+ * The MPM module assigns the connection ids and mod_unique_id is using
+ * that one to generate identifier for requests. While the implementation
+ * works for HTTP/1.x, the parallel execution of several requests per
+ * connection will generate duplicate identifiers on load.
+ *
+ * The original implementation for secondary connection identifiers used
+ * to shift the master connection id up and assign the stream id to the
+ * lower bits. This was cramped on 32 bit systems, but on 64bit there was
+ * enough space.
+ *
+ * As issue 195 showed, mod_unique_id only uses the lower 32 bit of the
+ * connection id, even on 64bit systems. Therefore collisions in request ids.
+ *
+ * The way master connection ids are generated, there is some space "at the
+ * top" of the lower 32 bits on allmost all systems. If you have a setup
+ * with 64k threads per child and 255 child processes, you live on the edge.
+ *
+ * The new implementation shifts 8 bits and XORs in the worker
+ * id. This will experience collisions with > 256 h2 workers and heavy
+ * load still. There seems to be no way to solve this in all possible
+ * configurations by mod_h2 alone.
+ */
+ if (c->master) {
+ c->id = (c->master->id << 8)^slot->id;
+ }
+ c->current_thread = thread;
+ AP_DEBUG_ASSERT(slot->prod);
- /* If this worker is the last one exiting and the MPM child is stopping,
- * unblock workers_pool_cleanup().
- */
- if (!apr_atomic_dec32(&workers->worker_count) && workers->aborted) {
- apr_thread_mutex_lock(workers->lock);
- apr_thread_cond_signal(workers->all_done);
- apr_thread_mutex_unlock(workers->lock);
- }
-}
+#if AP_HAS_RESPONSE_BUCKETS
+ ap_process_connection(c, ap_get_conn_socket(c));
+#else
+ h2_c2_process(c, thread, slot->id);
+#endif
+ slot->prod->fn_done(slot->prod->baton, c);
+
+ apr_thread_mutex_lock(workers->lock);
+ if (--slot->prod->conns_active <= 0) {
+ apr_thread_cond_broadcast(workers->prod_done);
+ }
+ if (slot->prod->state == PROD_IDLE) {
+ APR_RING_REMOVE(slot->prod, link);
+ slot->prod->state = PROD_ACTIVE;
+ APR_RING_INSERT_TAIL(&workers->prod_active, slot->prod, ap_conn_producer_t, link);
+ }
+ } while (!workers->aborted && !slot->should_shutdown);
+ APR_RING_REMOVE(slot, link); /* no longer busy */
+ }
-static void* APR_THREAD_FUNC slot_run(apr_thread_t *thread, void *wctx)
-{
- h2_slot *slot = wctx;
-
- /* Get the h2_task(s) from the ->mplxs queue. */
- while (get_next(slot)) {
- ap_assert(slot->task != NULL);
- do {
- 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->workers->aborted && --slot->sticks > 0) {
- h2_mplx_s_task_done(slot->task->mplx, slot->task, &slot->task);
- }
- else {
- h2_mplx_s_task_done(slot->task->mplx, slot->task, NULL);
- slot->task = NULL;
+ if (workers->aborted || slot->should_shutdown) {
+ break;
+ }
+
+ join_zombies(workers);
+
+ /* we are idle */
+ APR_RING_INSERT_TAIL(&workers->idle, slot, h2_slot, link);
+ ++workers->idle_slots;
+ slot->is_idle = 1;
+ if (slot->id >= workers->min_active && workers->idle_limit > 0) {
+ rv = apr_thread_cond_timedwait(slot->more_work, workers->lock,
+ workers->idle_limit);
+ if (APR_TIMEUP == rv) {
+ APR_RING_REMOVE(slot, link);
+ --workers->idle_slots;
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ "h2_workers: idle timeout slot %d in state %d (%d activations)",
+ slot->id, slot->state, slot->activations);
+ break;
}
- } while (slot->task);
+ }
+ else {
+ apr_thread_cond_wait(slot->more_work, workers->lock);
+ }
}
- if (!slot->timed_out) {
- slot_done(slot);
+ ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, workers->s,
+ "h2_workers: terminate slot %d in state %d (%d activations)",
+ slot->id, slot->state, slot->activations);
+ slot->is_idle = 0;
+ slot->state = H2_SLOT_ZOMBIE;
+ slot->should_shutdown = 0;
+ APR_RING_INSERT_TAIL(&workers->zombie, slot, h2_slot, link);
+ --workers->active_slots;
+ if (workers->active_slots <= 0) {
+ apr_thread_cond_broadcast(workers->all_done);
}
+ apr_thread_mutex_unlock(workers->lock);
apr_thread_exit(thread, APR_SUCCESS);
return NULL;
}
-static void wake_non_essential_workers(h2_workers *workers)
-{
- 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)
+static void wake_all_idles(h2_workers *workers)
{
h2_slot *slot;
-
- workers->shutdown = 1;
- workers->aborted = 1;
- h2_fifo_term(workers->mplxs);
-
- /* abort all idle slots */
- while ((slot = pop_slot(&workers->idle))) {
- apr_thread_mutex_lock(slot->lock);
- apr_thread_cond_signal(slot->not_idle);
- apr_thread_mutex_unlock(slot->lock);
+ for (slot = APR_RING_FIRST(&workers->idle);
+ slot != APR_RING_SENTINEL(&workers->idle, h2_slot, link);
+ slot = APR_RING_NEXT(slot, link))
+ {
+ apr_thread_cond_signal(slot->more_work);
}
}
@@ -321,40 +369,50 @@ static apr_status_t workers_pool_cleanup
h2_workers *workers = data;
apr_time_t end, timeout = apr_time_from_sec(1);
apr_status_t rv;
- int n, wait_sec = 5;
+ int n = 0, wait_sec = 5;
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
- "h2_workers: cleanup %d workers idling",
- (int)apr_atomic_read32(&workers->worker_count));
- workers_abort_idle(workers);
+ "h2_workers: cleanup %d workers (%d idle)",
+ workers->active_slots, workers->idle_slots);
+ apr_thread_mutex_lock(workers->lock);
+ workers->shutdown = 1;
+ workers->aborted = 1;
+ wake_all_idles(workers);
+ apr_thread_mutex_unlock(workers->lock);
/* wait for all the workers to become zombies and join them.
* this gets called after the mpm shuts down and all connections
* have either been handled (graceful) or we are forced exiting
* (ungrateful). Either way, we show limited patience. */
- apr_thread_mutex_lock(workers->lock);
end = apr_time_now() + apr_time_from_sec(wait_sec);
- while ((n = apr_atomic_read32(&workers->worker_count)) > 0
- && apr_time_now() < end) {
+ while (apr_time_now() < end) {
+ apr_thread_mutex_lock(workers->lock);
+ if (!(n = workers->active_slots)) {
+ apr_thread_mutex_unlock(workers->lock);
+ break;
+ }
+ wake_all_idles(workers);
rv = apr_thread_cond_timedwait(workers->all_done, workers->lock, timeout);
+ apr_thread_mutex_unlock(workers->lock);
+
if (APR_TIMEUP == rv) {
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
- APLOGNO(10290) "h2_workers: waiting for idle workers to close, "
- "still seeing %d workers living",
- apr_atomic_read32(&workers->worker_count));
- continue;
+ APLOGNO(10290) "h2_workers: waiting for workers to close, "
+ "still seeing %d workers (%d idle) living",
+ workers->active_slots, workers->idle_slots);
}
}
if (n) {
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, workers->s,
- APLOGNO(10291) "h2_workers: cleanup, %d idle workers "
+ APLOGNO(10291) "h2_workers: cleanup, %d workers (%d idle) "
"did not exit after %d seconds.",
- n, wait_sec);
+ n, workers->idle_slots, wait_sec);
}
- apr_thread_mutex_unlock(workers->lock);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
"h2_workers: cleanup all workers terminated");
+ apr_thread_mutex_lock(workers->lock);
join_zombies(workers);
+ apr_thread_mutex_unlock(workers->lock);
ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
"h2_workers: cleanup zombie workers joined");
@@ -362,23 +420,35 @@ static apr_status_t workers_pool_cleanup
}
h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pchild,
- int min_workers, int max_workers,
- int idle_secs)
+ int max_slots, int min_active,
+ apr_time_t idle_limit)
{
apr_status_t rv;
h2_workers *workers;
apr_pool_t *pool;
- int i, n;
+ apr_allocator_t *allocator;
+ int locked = 0;
+ apr_uint32_t i;
ap_assert(s);
ap_assert(pchild);
+ ap_assert(idle_limit > 0);
/* let's have our own pool that will be parent to all h2_worker
* instances we create. This happens in various threads, but always
* guarded by our lock. Without this pool, all subpool creations would
* happen on the pool handed to us, which we do not guard.
*/
- apr_pool_create(&pool, pchild);
+ rv = apr_allocator_create(&allocator);
+ if (rv != APR_SUCCESS) {
+ goto cleanup;
+ }
+ rv = apr_pool_create_ex(&pool, pchild, NULL, allocator);
+ if (rv != APR_SUCCESS) {
+ apr_allocator_destroy(allocator);
+ goto cleanup;
+ }
+ apr_allocator_owner_set(allocator, pool);
apr_pool_tag(pool, "h2_workers");
workers = apr_pcalloc(pool, sizeof(h2_workers));
if (!workers) {
@@ -387,27 +457,23 @@ h2_workers *h2_workers_create(server_rec
workers->s = s;
workers->pool = pool;
- workers->min_workers = min_workers;
- workers->max_workers = max_workers;
- workers->max_idle_duration = apr_time_from_sec((idle_secs > 0)? idle_secs : 10);
+ workers->min_active = min_active;
+ workers->max_slots = max_slots;
+ workers->idle_limit = idle_limit;
+ workers->dynamic = (workers->min_active < workers->max_slots);
+
+ ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
+ "h2_workers: created with min=%d max=%d idle_ms=%d",
+ workers->min_active, workers->max_slots,
+ (int)apr_time_as_msec(idle_limit));
+
+ APR_RING_INIT(&workers->idle, h2_slot, link);
+ APR_RING_INIT(&workers->busy, h2_slot, link);
+ APR_RING_INIT(&workers->free, h2_slot, link);
+ APR_RING_INIT(&workers->zombie, h2_slot, link);
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, workers->s,
- "h2_workers: created with min=%d max=%d idle_timeout=%d sec",
- workers->min_workers, workers->max_workers,
- (int)apr_time_sec(workers->max_idle_duration));
- /* FIXME: the fifo set we use here has limited capacity. Once the
- * set is full, connections with new requests do a wait. Unfortunately,
- * we have optimizations in place there that makes such waiting "unfair"
- * in the sense that it may take connections a looong time to get scheduled.
- *
- * Need to rewrite this to use one of our double-linked lists and a mutex
- * to have unlimited capacity and fair scheduling.
- *
- * For now, we just make enough room to have many connections inside one
- * process.
- */
- rv = h2_fifo_set_create(&workers->mplxs, pool, 8 * 1024);
- if (rv != APR_SUCCESS) goto cleanup;
+ APR_RING_INIT(&workers->prod_active, ap_conn_producer_t, link);
+ APR_RING_INIT(&workers->prod_idle, ap_conn_producer_t, link);
rv = apr_threadattr_create(&workers->thread_attr, workers->pool);
if (rv != APR_SUCCESS) goto cleanup;
@@ -426,32 +492,35 @@ h2_workers *h2_workers_create(server_rec
if (rv != APR_SUCCESS) goto cleanup;
rv = apr_thread_cond_create(&workers->all_done, workers->pool);
if (rv != APR_SUCCESS) goto cleanup;
+ rv = apr_thread_cond_create(&workers->prod_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) {
+ apr_thread_mutex_lock(workers->lock);
+ locked = 1;
+
+ /* create the slots and put them on the free list */
+ workers->slots = apr_pcalloc(workers->pool, workers->max_slots * sizeof(h2_slot));
+
+ for (i = 0; i < workers->max_slots; ++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]);
+ workers->slots[i].state = H2_SLOT_FREE;
+ workers->slots[i].workers = workers;
+ APR_RING_ELEM_INIT(&workers->slots[i], link);
+ APR_RING_INSERT_TAIL(&workers->free, &workers->slots[i], h2_slot, link);
+ rv = apr_thread_cond_create(&workers->slots[i].more_work, workers->pool);
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]);
+
+ /* activate the min amount of workers */
+ for (i = 0; i < workers->min_active; ++i) {
+ rv = activate_slot(workers);
+ if (rv != APR_SUCCESS) goto cleanup;
}
- workers->dynamic = (workers->worker_count < workers->max_workers);
cleanup:
+ if (locked) {
+ apr_thread_mutex_unlock(workers->lock);
+ }
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
@@ -461,24 +530,97 @@ cleanup:
apr_pool_pre_cleanup_register(pchild, workers, workers_pool_cleanup);
return workers;
}
+ ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s,
+ "h2_workers: errors initializing");
return NULL;
}
-apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m)
+apr_uint32_t h2_workers_get_max_workers(h2_workers *workers)
+{
+ return workers->max_slots;
+}
+
+void h2_workers_shutdown(h2_workers *workers, int graceful)
{
- apr_status_t status = h2_fifo_push(workers->mplxs, m);
- wake_idle_worker(workers);
- return status;
+ ap_conn_producer_t *prod;
+
+ apr_thread_mutex_lock(workers->lock);
+ ap_log_error(APLOG_MARK, APLOG_TRACE1, 0, workers->s,
+ "h2_workers: shutdown graceful=%d", graceful);
+ workers->shutdown = 1;
+ workers->idle_limit = apr_time_from_sec(1);
+ wake_all_idles(workers);
+ for (prod = APR_RING_FIRST(&workers->prod_idle);
+ prod != APR_RING_SENTINEL(&workers->prod_idle, ap_conn_producer_t, link);
+ prod = APR_RING_NEXT(prod, link)) {
+ if (prod->fn_shutdown) {
+ prod->fn_shutdown(prod->baton, graceful);
+ }
+ }
+ apr_thread_mutex_unlock(workers->lock);
}
-apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m)
+ap_conn_producer_t *h2_workers_register(h2_workers *workers,
+ apr_pool_t *producer_pool,
+ const char *name,
+ ap_conn_producer_next *fn_next,
+ ap_conn_producer_done *fn_done,
+ ap_conn_producer_shutdown *fn_shutdown,
+ void *baton)
+{
+ ap_conn_producer_t *prod;
+
+ prod = apr_pcalloc(producer_pool, sizeof(*prod));
+ APR_RING_ELEM_INIT(prod, link);
+ prod->name = name;
+ prod->fn_next = fn_next;
+ prod->fn_done = fn_done;
+ prod->fn_shutdown = fn_shutdown;
+ prod->baton = baton;
+
+ apr_thread_mutex_lock(workers->lock);
+ prod->state = PROD_IDLE;
+ APR_RING_INSERT_TAIL(&workers->prod_idle, prod, ap_conn_producer_t, link);
+ apr_thread_mutex_unlock(workers->lock);
+
+ return prod;
+}
+
+apr_status_t h2_workers_join(h2_workers *workers, ap_conn_producer_t *prod)
{
- return h2_fifo_remove(workers->mplxs, m);
+ apr_status_t rv = APR_SUCCESS;
+
+ apr_thread_mutex_lock(workers->lock);
+ if (PROD_JOINED == prod->state) {
+ AP_DEBUG_ASSERT(APR_RING_NEXT(prod, link) == prod); /* should be in no ring */
+ rv = APR_EINVAL;
+ }
+ else {
+ AP_DEBUG_ASSERT(PROD_ACTIVE == prod->state || PROD_IDLE == prod->state);
+ APR_RING_REMOVE(prod, link);
+ prod->state = PROD_JOINED; /* prevent further activations */
+ while (prod->conns_active > 0) {
+ apr_thread_cond_wait(workers->prod_done, workers->lock);
+ }
+ APR_RING_ELEM_INIT(prod, link); /* make it link to itself */
+ }
+ apr_thread_mutex_unlock(workers->lock);
+ return rv;
}
-void h2_workers_graceful_shutdown(h2_workers *workers)
+apr_status_t h2_workers_activate(h2_workers *workers, ap_conn_producer_t *prod)
{
- workers->shutdown = 1;
- workers->max_idle_duration = apr_time_from_sec(1);
- wake_non_essential_workers(workers);
+ apr_status_t rv = APR_SUCCESS;
+ apr_thread_mutex_lock(workers->lock);
+ if (PROD_IDLE == prod->state) {
+ APR_RING_REMOVE(prod, link);
+ prod->state = PROD_ACTIVE;
+ APR_RING_INSERT_TAIL(&workers->prod_active, prod, ap_conn_producer_t, link);
+ wake_idle_worker(workers, prod);
+ }
+ else if (PROD_JOINED == prod->state) {
+ rv = APR_EINVAL;
+ }
+ apr_thread_mutex_unlock(workers->lock);
+ return rv;
}
Modified: httpd/httpd/branches/2.4.x/modules/http2/h2_workers.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/http2/h2_workers.h?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/http2/h2_workers.h (original)
+++ httpd/httpd/branches/2.4.x/modules/http2/h2_workers.h Mon Jan 9 07:35:18 2023
@@ -17,73 +17,113 @@
#ifndef __mod_h2__h2_workers__
#define __mod_h2__h2_workers__
-/* Thread pool specific to executing h2_tasks. Has a minimum and maximum
- * number of workers it creates. Starts with minimum workers and adds
- * some on load, reduces the number again when idle.
- *
+/* Thread pool specific to executing secondary connections.
+ * Has a minimum and maximum number of workers it creates.
+ * Starts with minimum workers and adds some on load,
+ * reduces the number again when idle.
*/
struct apr_thread_mutex_t;
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;
-struct h2_workers {
- server_rec *s;
- apr_pool_t *pool;
-
- int next_worker_id;
- apr_uint32_t max_workers;
- volatile apr_uint32_t min_workers; /* is changed during graceful shutdown */
- volatile apr_interval_time_t max_idle_duration; /* is changed during graceful shutdown */
-
- volatile int aborted;
- volatile int shutdown;
- 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;
-
- struct h2_fifo *mplxs;
-
- struct apr_thread_mutex_t *lock;
- struct apr_thread_cond_t *all_done;
-};
-
-/* Create a worker pool with the given minimum and maximum number of
- * threads.
+/**
+ * Create a worker set with a maximum number of 'slots', e.g. worker
+ * threads to run. Always keep `min_active` workers running. Shutdown
+ * any additional workers after `idle_secs` seconds of doing nothing.
+ *
+ * @oaram s the base server
+ * @param pool for allocations
+ * @param min_active minimum number of workers to run
+ * @param max_slots maximum number of worker slots
+ * @param idle_limit upper duration of idle after a non-minimal slots shuts down
*/
h2_workers *h2_workers_create(server_rec *s, apr_pool_t *pool,
- int min_size, int max_size, int idle_secs);
+ int max_slots, int min_active, apr_time_t idle_limit);
+
+/**
+ * Shut down processing.
+ */
+void h2_workers_shutdown(h2_workers *workers, int graceful);
+
+/**
+ * Get the maximum number of workers.
+ */
+apr_uint32_t h2_workers_get_max_workers(h2_workers *workers);
+
+/**
+ * ap_conn_producer_t is the source of connections (conn_rec*) to run.
+ *
+ * Active producers are queried by idle workers for connections.
+ * If they do not hand one back, they become inactive and are not
+ * queried further. `h2_workers_activate()` places them on the active
+ * list again.
+ *
+ * A producer finishing MUST call `h2_workers_join()` which removes
+ * it completely from workers processing and waits for all ongoing
+ * work for this producer to be done.
+ */
+typedef struct ap_conn_producer_t ap_conn_producer_t;
+
+/**
+ * Ask a producer for the next connection to process.
+ * @param baton value from producer registration
+ * @param pconn holds the connection to process on return
+ * @param pmore if the producer has more connections that may be retrieved
+ * @return APR_SUCCESS for a connection to process, APR_EAGAIN for no
+ * connection being available at the time.
+ */
+typedef conn_rec *ap_conn_producer_next(void *baton, int *pmore);
+
+/**
+ * Tell the producer that processing the connection is done.
+ * @param baton value from producer registration
+ * @param conn the connection that has been processed.
+ */
+typedef void ap_conn_producer_done(void *baton, conn_rec *conn);
+
+/**
+ * Tell the producer that the workers are shutting down.
+ * @param baton value from producer registration
+ * @param graceful != 0 iff shutdown is graceful
+ */
+typedef void ap_conn_producer_shutdown(void *baton, int graceful);
/**
- * Registers a h2_mplx for task scheduling. If this h2_mplx runs
- * out of tasks, it will be automatically be unregistered. Should
- * new tasks arrive, it needs to be registered again.
+ * Register a new producer with the given `baton` and callback functions.
+ * Will allocate internal structures from the given pool (but make no use
+ * of the pool after registration).
+ * Producers are inactive on registration. See `h2_workers_activate()`.
+ * @param producer_pool to allocate the producer from
+ * @param name descriptive name of the producer, must not be unique
+ * @param fn_next callback for retrieving connections to process
+ * @param fn_done callback for processed connections
+ * @param baton provided value passed on in callbacks
+ * @return the producer instance created
*/
-apr_status_t h2_workers_register(h2_workers *workers, struct h2_mplx *m);
+ap_conn_producer_t *h2_workers_register(h2_workers *workers,
+ apr_pool_t *producer_pool,
+ const char *name,
+ ap_conn_producer_next *fn_next,
+ ap_conn_producer_done *fn_done,
+ ap_conn_producer_shutdown *fn_shutdown,
+ void *baton);
/**
- * Remove a h2_mplx from the worker registry.
+ * Stop retrieving more connection from the producer and wait
+ * for all ongoing for from that producer to be done.
*/
-apr_status_t h2_workers_unregister(h2_workers *workers, struct h2_mplx *m);
+apr_status_t h2_workers_join(h2_workers *workers, ap_conn_producer_t *producer);
/**
- * Shut down processing gracefully by terminating all idle workers.
+ * Activate a producer. A worker will query the producer for a connection
+ * to process, once a worker is available.
+ * This may be called, irregardless of the producers active/inactive.
*/
-void h2_workers_graceful_shutdown(h2_workers *workers);
+apr_status_t h2_workers_activate(h2_workers *workers, ap_conn_producer_t *producer);
#endif /* defined(__mod_h2__h2_workers__) */
Modified: httpd/httpd/branches/2.4.x/modules/http2/mod_http2.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/http2/mod_http2.c?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/http2/mod_http2.c (original)
+++ httpd/httpd/branches/2.4.x/modules/http2/mod_http2.c Mon Jan 9 07:35:18 2023
@@ -30,19 +30,18 @@
#include <nghttp2/nghttp2.h>
#include "h2_stream.h"
-#include "h2_alt_svc.h"
-#include "h2_conn.h"
-#include "h2_filter.h"
-#include "h2_task.h"
+#include "h2_c1.h"
+#include "h2_c2.h"
#include "h2_session.h"
#include "h2_config.h"
-#include "h2_ctx.h"
-#include "h2_h2.h"
+#include "h2_conn_ctx.h"
+#include "h2_protocol.h"
#include "h2_mplx.h"
#include "h2_push.h"
#include "h2_request.h"
#include "h2_switch.h"
#include "h2_version.h"
+#include "h2_bucket_beam.h"
static void h2_hooks(apr_pool_t *pool);
@@ -126,27 +125,6 @@ static int h2_post_config(apr_pool_t *p,
myfeats.dyn_windows? "+DWINS" : "",
ngh2? ngh2->version_str : "unknown");
- switch (h2_conn_mpm_type()) {
- case H2_MPM_SIMPLE:
- case H2_MPM_MOTORZ:
- case H2_MPM_NETWARE:
- case H2_MPM_WINNT:
- /* not sure we need something extra for those. */
- break;
- case H2_MPM_EVENT:
- case H2_MPM_WORKER:
- /* all fine, we know these ones */
- break;
- case H2_MPM_PREFORK:
- /* ok, we now know how to handle that one */
- break;
- case H2_MPM_UNKNOWN:
- /* ??? */
- ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(03091)
- "post_config: mpm type unknown");
- break;
- }
-
if (!h2_mpm_supported() && !mpm_warned) {
mpm_warned = 1;
ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, APLOGNO(10034)
@@ -158,14 +136,11 @@ static int h2_post_config(apr_pool_t *p,
h2_conn_mpm_name());
}
- status = h2_h2_init(p, s);
+ status = h2_protocol_init(p, s);
if (status == APR_SUCCESS) {
status = h2_switch_init(p, s);
}
- if (status == APR_SUCCESS) {
- status = h2_task_init(p, s);
- }
-
+
return status;
}
@@ -175,7 +150,9 @@ static int http2_is_h2(conn_rec *);
static void http2_get_num_workers(server_rec *s, int *minw, int *maxw)
{
- h2_get_num_workers(s, minw, maxw);
+ apr_time_t tdummy;
+
+ h2_get_workers_config(s, minw, maxw, &tdummy);
}
/* Runs once per created child process. Perform any process
@@ -183,29 +160,15 @@ static void http2_get_num_workers(server
*/
static void h2_child_init(apr_pool_t *pchild, server_rec *s)
{
- apr_allocator_t *allocator;
- apr_thread_mutex_t *mutex;
- apr_status_t status;
-
- /* The allocator of pchild has no mutex with MPM prefork, but we need one
- * for h2 workers threads synchronization. Even though mod_http2 shouldn't
- * be used with prefork, better be safe than sorry, so forcibly set the
- * mutex here. For MPM event/worker, pchild has no allocator so pconf's
- * is used, with its mutex.
- */
- allocator = apr_pool_allocator_get(pchild);
- if (allocator) {
- mutex = apr_allocator_mutex_get(allocator);
- if (!mutex) {
- apr_thread_mutex_create(&mutex, APR_THREAD_MUTEX_DEFAULT, pchild);
- apr_allocator_mutex_set(allocator, mutex);
- }
- }
+ apr_status_t rv;
/* Set up our connection processing */
- status = h2_conn_child_init(pchild, s);
- if (status != APR_SUCCESS) {
- ap_log_error(APLOG_MARK, APLOG_ERR, status, s,
+ rv = h2_c1_child_init(pchild, s);
+ if (APR_SUCCESS == rv) {
+ rv = h2_c2_child_init(pchild, s);
+ }
+ if (APR_SUCCESS != rv) {
+ ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
APLOGNO(02949) "initializing connection handling");
}
}
@@ -230,41 +193,38 @@ static void h2_hooks(apr_pool_t *pool)
*/
ap_hook_child_init(h2_child_init, NULL, NULL, APR_HOOK_MIDDLE);
#if AP_MODULE_MAGIC_AT_LEAST(20120211, 110)
- ap_hook_child_stopping(h2_conn_child_stopping, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_child_stopping(h2_c1_child_stopping, NULL, NULL, APR_HOOK_MIDDLE);
#endif
- h2_h2_register_hooks();
+
+ h2_c1_register_hooks();
h2_switch_register_hooks();
- h2_task_register_hooks();
+ h2_c2_register_hooks();
- h2_alt_svc_register_hooks();
-
- /* Setup subprocess env for certain variables
+ /* Setup subprocess env for certain variables
*/
ap_hook_fixups(h2_h2_fixups, NULL,NULL, APR_HOOK_MIDDLE);
-
- /* test http2 connection status handler */
- ap_hook_handler(h2_filter_h2_status_handler, NULL, NULL, APR_HOOK_MIDDLE);
}
static const char *val_HTTP2(apr_pool_t *p, server_rec *s,
- conn_rec *c, request_rec *r, h2_ctx *ctx)
+ conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx)
{
return ctx? "on" : "off";
}
static const char *val_H2_PUSH(apr_pool_t *p, server_rec *s,
- conn_rec *c, request_rec *r, h2_ctx *ctx)
+ conn_rec *c, request_rec *r,
+ h2_conn_ctx_t *conn_ctx)
{
- if (ctx) {
+ if (conn_ctx) {
if (r) {
- if (ctx->task) {
- h2_stream *stream = h2_mplx_t_stream_get(ctx->task->mplx, ctx->task);
+ if (conn_ctx->stream_id) {
+ const h2_stream *stream = h2_mplx_c2_stream_get(conn_ctx->mplx, conn_ctx->stream_id);
if (stream && stream->push_policy != H2_PUSH_NONE) {
return "on";
}
}
}
- else if (c && h2_session_push_enabled(ctx->session)) {
+ else if (c && h2_session_push_enabled(conn_ctx->session)) {
return "on";
}
}
@@ -277,10 +237,11 @@ static const char *val_H2_PUSH(apr_pool_
}
static const char *val_H2_PUSHED(apr_pool_t *p, server_rec *s,
- conn_rec *c, request_rec *r, h2_ctx *ctx)
+ conn_rec *c, request_rec *r,
+ h2_conn_ctx_t *conn_ctx)
{
- if (ctx) {
- if (ctx->task && !H2_STREAM_CLIENT_INITIATED(ctx->task->stream_id)) {
+ if (conn_ctx) {
+ if (conn_ctx->stream_id && !H2_STREAM_CLIENT_INITIATED(conn_ctx->stream_id)) {
return "PUSHED";
}
}
@@ -288,11 +249,12 @@ static const char *val_H2_PUSHED(apr_poo
}
static const char *val_H2_PUSHED_ON(apr_pool_t *p, server_rec *s,
- conn_rec *c, request_rec *r, h2_ctx *ctx)
+ conn_rec *c, request_rec *r,
+ h2_conn_ctx_t *conn_ctx)
{
- if (ctx) {
- if (ctx->task && !H2_STREAM_CLIENT_INITIATED(ctx->task->stream_id)) {
- h2_stream *stream = h2_mplx_t_stream_get(ctx->task->mplx, ctx->task);
+ if (conn_ctx) {
+ if (conn_ctx->stream_id && !H2_STREAM_CLIENT_INITIATED(conn_ctx->stream_id)) {
+ const h2_stream *stream = h2_mplx_c2_stream_get(conn_ctx->mplx, conn_ctx->stream_id);
if (stream) {
return apr_itoa(p, stream->initiated_on);
}
@@ -302,28 +264,30 @@ static const char *val_H2_PUSHED_ON(apr_
}
static const char *val_H2_STREAM_TAG(apr_pool_t *p, server_rec *s,
- conn_rec *c, request_rec *r, h2_ctx *ctx)
+ conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx)
{
- if (ctx) {
- if (ctx->task) {
- return ctx->task->id;
+ if (c) {
+ h2_conn_ctx_t *conn_ctx = h2_conn_ctx_get(c);
+ if (conn_ctx) {
+ return conn_ctx->stream_id == 0? conn_ctx->id
+ : apr_psprintf(p, "%s-%d", conn_ctx->id, conn_ctx->stream_id);
}
}
return "";
}
static const char *val_H2_STREAM_ID(apr_pool_t *p, server_rec *s,
- conn_rec *c, request_rec *r, h2_ctx *ctx)
+ conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx)
{
const char *cp = val_H2_STREAM_TAG(p, s, c, r, ctx);
- if (cp && (cp = ap_strchr_c(cp, '-'))) {
+ if (cp && (cp = ap_strrchr_c(cp, '-'))) {
return ++cp;
}
return NULL;
}
typedef const char *h2_var_lookup(apr_pool_t *p, server_rec *s,
- conn_rec *c, request_rec *r, h2_ctx *ctx);
+ conn_rec *c, request_rec *r, h2_conn_ctx_t *ctx);
typedef struct h2_var_def {
const char *name;
h2_var_lookup *lookup;
@@ -347,19 +311,19 @@ static h2_var_def H2_VARS[] = {
static int http2_is_h2(conn_rec *c)
{
- return h2_ctx_get(c->master? c->master : c, 0) != NULL;
+ return h2_conn_ctx_get(c->master? c->master : c) != NULL;
}
static char *http2_var_lookup(apr_pool_t *p, server_rec *s,
conn_rec *c, request_rec *r, char *name)
{
- int i;
+ unsigned int i;
/* If the # of vars grow, we need to put definitions in a hash */
for (i = 0; i < H2_ALEN(H2_VARS); ++i) {
h2_var_def *vdef = &H2_VARS[i];
if (!strcmp(vdef->name, name)) {
- h2_ctx *ctx = (r? h2_ctx_get(c, 0) :
- h2_ctx_get(c->master? c->master : c, 0));
+ h2_conn_ctx_t *ctx = (r? h2_conn_ctx_get(c) :
+ h2_conn_ctx_get(c->master? c->master : c));
return (char *)vdef->lookup(p, s, c, r, ctx);
}
}
@@ -369,9 +333,9 @@ static char *http2_var_lookup(apr_pool_t
static int h2_h2_fixups(request_rec *r)
{
if (r->connection->master) {
- h2_ctx *ctx = h2_ctx_get(r->connection, 0);
- int i;
-
+ h2_conn_ctx_t *ctx = h2_conn_ctx_get(r->connection);
+ unsigned int i;
+
for (i = 0; ctx && i < H2_ALEN(H2_VARS); ++i) {
h2_var_def *vdef = &H2_VARS[i];
if (vdef->subprocess) {
Modified: httpd/httpd/branches/2.4.x/modules/http2/mod_http2.dsp
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/http2/mod_http2.dsp?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/http2/mod_http2.dsp (original)
+++ httpd/httpd/branches/2.4.x/modules/http2/mod_http2.dsp Mon Jan 9 07:35:18 2023
@@ -101,10 +101,6 @@ PostBuild_Cmds=if exist $(TargetPath).ma
# Name "mod_http2 - Win32 Debug"
# Begin Source File
-SOURCE=./h2_alt_svc.c
-# End Source File
-# Begin Source File
-
SOURCE=./h2_bucket_beam.c
# End Source File
# Begin Source File
@@ -113,31 +109,31 @@ SOURCE=./h2_bucket_eos.c
# End Source File
# Begin Source File
-SOURCE=./h2_config.c
+SOURCE=./h2_c1.c
# End Source File
# Begin Source File
-SOURCE=./h2_conn.c
+SOURCE=./h2_c1_io.c
# End Source File
# Begin Source File
-SOURCE=./h2_conn_io.c
+SOURCE=./h2_c2.c
# End Source File
# Begin Source File
-SOURCE=./h2_ctx.c
+SOURCE=./h2_c2_filter.c
# End Source File
# Begin Source File
-SOURCE=./h2_filter.c
+SOURCE=./h2_config.c
# End Source File
# Begin Source File
-SOURCE=./h2_from_h1.c
+SOURCE=./h2_conn_ctx.c
# End Source File
# Begin Source File
-SOURCE=./h2_h2.c
+SOURCE=./h2_headers.c
# End Source File
# Begin Source File
@@ -145,15 +141,15 @@ SOURCE=./h2_mplx.c
# End Source File
# Begin Source File
-SOURCE=./h2_push.c
+SOURCE=./h2_protocol.c
# End Source File
# Begin Source File
-SOURCE=./h2_request.c
+SOURCE=./h2_push.c
# End Source File
# Begin Source File
-SOURCE=./h2_headers.c
+SOURCE=./h2_request.c
# End Source File
# Begin Source File
@@ -169,15 +165,15 @@ SOURCE=./h2_switch.c
# End Source File
# Begin Source File
-SOURCE=./h2_task.c
+SOURCE=./h2_util.c
# End Source File
# Begin Source File
-SOURCE=./h2_util.c
+SOURCE=./h2_workers.c
# End Source File
# Begin Source File
-SOURCE=./h2_workers.c
+SOURCE=./mod_http2.c
# End Source File
# Begin Source File
Modified: httpd/httpd/branches/2.4.x/modules/http2/mod_http2.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/http2/mod_http2.h?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/http2/mod_http2.h (original)
+++ httpd/httpd/branches/2.4.x/modules/http2/mod_http2.h Mon Jan 9 07:35:18 2023
@@ -28,6 +28,9 @@ APR_DECLARE_OPTIONAL_FN(char *,
APR_DECLARE_OPTIONAL_FN(int,
http2_is_h2, (conn_rec *));
+APR_DECLARE_OPTIONAL_FN(void,
+ http2_get_num_workers, (server_rec *s,
+ int *minw, int *max));
/*******************************************************************************
* START HTTP/2 request engines (DEPRECATED)
@@ -68,9 +71,6 @@ APR_DECLARE_OPTIONAL_FN(void,
conn_rec *rconn,
apr_status_t status));
-APR_DECLARE_OPTIONAL_FN(void,
- http2_get_num_workers, (server_rec *s,
- int *minw, int *max));
/*******************************************************************************
* END HTTP/2 request engines (DEPRECATED)
Modified: httpd/httpd/branches/2.4.x/modules/http2/mod_proxy_http2.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/http2/mod_proxy_http2.c?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/http2/mod_proxy_http2.c (original)
+++ httpd/httpd/branches/2.4.x/modules/http2/mod_proxy_http2.c Mon Jan 9 07:35:18 2023
@@ -249,7 +249,7 @@ static apr_status_t ctx_run(h2_proxy_ctx
ctx->r_done = 0;
add_request(ctx->session, ctx->r);
- while (!ctx->master->aborted && !ctx->r_done) {
+ while (!ctx->owner->aborted && !ctx->r_done) {
status = h2_proxy_session_process(ctx->session);
if (status != APR_SUCCESS) {
@@ -267,16 +267,13 @@ static apr_status_t ctx_run(h2_proxy_ctx
}
out:
- if (ctx->master->aborted) {
+ if (ctx->owner->aborted) {
/* master connection gone */
ap_log_cerror(APLOG_MARK, APLOG_DEBUG, status, ctx->owner,
APLOGNO(03374) "eng(%s): master connection gone", ctx->id);
/* cancel all ongoing requests */
h2_proxy_session_cancel_all(ctx->session);
h2_proxy_session_process(ctx->session);
- if (!ctx->master->aborted) {
- status = ctx->r_status = APR_SUCCESS;
- }
}
ctx->session->user_data = NULL;
@@ -291,7 +288,7 @@ static int proxy_http2_handler(request_r
const char *proxyname,
apr_port_t proxyport)
{
- const char *proxy_func, *task_id;
+ const char *proxy_func;
char *locurl = url, *u;
apr_size_t slen;
int is_ssl = 0;
@@ -324,11 +321,9 @@ static int proxy_http2_handler(request_r
return DECLINED;
}
- task_id = apr_table_get(r->connection->notes, H2_TASK_ID_NOTE);
-
ctx = apr_pcalloc(r->pool, sizeof(*ctx));
ctx->master = r->connection->master? r->connection->master : r->connection;
- ctx->id = task_id? task_id : apr_psprintf(r->pool, "%ld", (long)ctx->master->id);
+ ctx->id = apr_psprintf(r->pool, "%ld", (long)ctx->master->id);
ctx->owner = r->connection;
ctx->pool = r->pool;
ctx->server = r->server;
@@ -350,7 +345,7 @@ static int proxy_http2_handler(request_r
"H2: serving URL %s", url);
run_connect:
- if (ctx->master->aborted) goto cleanup;
+ if (ctx->owner->aborted) goto cleanup;
/* Get a proxy_conn_rec from the worker, might be a new one, might
* be one still open from another request, or it might fail if the
@@ -402,10 +397,10 @@ run_connect:
"proxy-request-alpn-protos", "h2");
}
- if (ctx->master->aborted) goto cleanup;
+ if (ctx->owner->aborted) goto cleanup;
status = ctx_run(ctx);
- if (ctx->r_status != APR_SUCCESS && ctx->r_may_retry && !ctx->master->aborted) {
+ if (ctx->r_status != APR_SUCCESS && ctx->r_may_retry && !ctx->owner->aborted) {
/* Not successfully processed, but may retry, tear down old conn and start over */
if (ctx->p_conn) {
ctx->p_conn->close = 1;
Modified: httpd/httpd/branches/2.4.x/test/conftest.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/conftest.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/conftest.py (original)
+++ httpd/httpd/branches/2.4.x/test/conftest.py Mon Jan 9 07:35:18 2023
@@ -1,6 +1,8 @@
import sys
import os
+import pytest
+
sys.path.append(os.path.join(os.path.dirname(__file__), '.'))
from pyhttpd.env import HttpdTestEnv
@@ -9,4 +11,21 @@ def pytest_report_header(config, startdi
env = HttpdTestEnv()
return f"[apache httpd: {env.get_httpd_version()}, mpm: {env.mpm_module}, {env.prefix}]"
+def pytest_addoption(parser):
+ parser.addoption("--repeat", action="store", type=int, default=1,
+ help='Number of times to repeat each test')
+ parser.addoption("--all", action="store_true")
+
+
+def pytest_generate_tests(metafunc):
+ if "repeat" in metafunc.fixturenames:
+ count = int(metafunc.config.getoption("repeat"))
+ metafunc.fixturenames.append('tmp_ct')
+ metafunc.parametrize('repeat', range(count))
+
+@pytest.fixture(autouse=True, scope="function")
+def _function_scope(env, request):
+ env.set_current_test_name(request.node.name)
+ yield
+ env.set_current_test_name(None)
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/conftest.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/conftest.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/conftest.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/conftest.py Mon Jan 9 07:35:18 2023
@@ -14,19 +14,6 @@ def pytest_report_header(config, startdi
return f"mod_h2 [apache: {env.get_httpd_version()}, mpm: {env.mpm_module}, {env.prefix}]"
-def pytest_addoption(parser):
- parser.addoption("--repeat", action="store", type=int, default=1,
- help='Number of times to repeat each test')
- parser.addoption("--all", action="store_true")
-
-
-def pytest_generate_tests(metafunc):
- if "repeat" in metafunc.fixturenames:
- count = int(metafunc.config.getoption("repeat"))
- metafunc.fixturenames.append('tmp_ct')
- metafunc.parametrize('repeat', range(count))
-
-
@pytest.fixture(scope="package")
def env(pytestconfig) -> H2TestEnv:
level = logging.INFO
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/env.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/env.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/env.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/env.py Mon Jan 9 07:35:18 2023
@@ -57,14 +57,20 @@ class H2TestSetup(HttpdTestSetup):
class H2TestEnv(HttpdTestEnv):
+ @classmethod
+ @property
+ def is_unsupported(cls):
+ mpm_module = f"mpm_{os.environ['MPM']}" if 'MPM' in os.environ else 'mpm_event'
+ return mpm_module == 'mpm_prefork'
+
def __init__(self, pytestconfig=None):
super().__init__(pytestconfig=pytestconfig)
self.add_httpd_conf([
- "H2MinWorkers 1",
- "H2MaxWorkers 64",
- "Protocols h2 http/1.1 h2c",
- ])
- self.add_httpd_log_modules(["http2", "proxy_http2", "h2test"])
+ "H2MinWorkers 1",
+ "H2MaxWorkers 64",
+ "Protocols h2 http/1.1 h2c",
+ ])
+ self.add_httpd_log_modules(["http2", "proxy_http2", "h2test", "proxy", "proxy_http"])
self.add_cert_specs([
CertificateSpec(domains=[
f"push.{self._http_tld}",
@@ -86,7 +92,12 @@ class H2TestEnv(HttpdTestEnv):
'AH00135',
'AH02261', # Re-negotiation handshake failed (our test_101)
'AH03490', # scoreboard full, happens on limit tests
- 'AH01247', # cgi reports sometimes error on accept on cgid socket
+ 'AH02429', # invalid chars in response header names, see test_h2_200
+ 'AH02430', # invalid chars in response header values, see test_h2_200
+ 'AH10373', # SSL errors on uncompleted handshakes, see test_h2_105
+ 'AH01247', # mod_cgid sometimes freaks out on load tests
+ 'AH01110', # error by proxy reading response
+ 'AH10400', # warning that 'enablereuse' has not effect in certain configs test_h2_600
])
self.httpd_error_log.add_ignored_patterns([
re.compile(r'.*malformed header from script \'hecho.py\': Bad header: x.*'),
@@ -112,6 +123,15 @@ class H2Conf(HttpdConf):
f"cgi.{env.http_tld}": [
"SSLOptions +StdEnvVars",
"AddHandler cgi-script .py",
+ "<Location \"/h2test/echo\">",
+ " SetHandler h2test-echo",
+ "</Location>",
+ "<Location \"/h2test/delay\">",
+ " SetHandler h2test-delay",
+ "</Location>",
+ "<Location \"/h2test/error\">",
+ " SetHandler h2test-error",
+ "</Location>",
]
}))
Added: httpd/httpd/branches/2.4.x/test/modules/http2/htdocs/cgi/alive.json
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/htdocs/cgi/alive.json?rev=1906475&view=auto
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/htdocs/cgi/alive.json (added)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/htdocs/cgi/alive.json Mon Jan 9 07:35:18 2023
@@ -0,0 +1,4 @@
+{
+ "host" : "cgi",
+ "alive" : true
+}
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/htdocs/cgi/hello.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/htdocs/cgi/hello.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/htdocs/cgi/hello.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/htdocs/cgi/hello.py Mon Jan 9 07:35:18 2023
@@ -6,11 +6,15 @@ print("Content-Type: application/json")
print()
print("{")
print(" \"https\" : \"%s\"," % (os.getenv('HTTPS', '')))
-print(" \"host\" : \"%s\"," % (os.getenv('SERVER_NAME', '')))
+print(" \"host\" : \"%s\"," % (os.getenv('X_HOST', '') \
+ if 'X_HOST' in os.environ else os.getenv('SERVER_NAME', '')))
+print(" \"server\" : \"%s\"," % (os.getenv('SERVER_NAME', '')))
+print(" \"h2_original_host\" : \"%s\"," % (os.getenv('H2_ORIGINAL_HOST', '')))
print(" \"port\" : \"%s\"," % (os.getenv('SERVER_PORT', '')))
print(" \"protocol\" : \"%s\"," % (os.getenv('SERVER_PROTOCOL', '')))
print(" \"ssl_protocol\" : \"%s\"," % (os.getenv('SSL_PROTOCOL', '')))
print(" \"h2\" : \"%s\"," % (os.getenv('HTTP2', '')))
-print(" \"h2push\" : \"%s\"" % (os.getenv('H2PUSH', '')))
+print(" \"h2push\" : \"%s\"," % (os.getenv('H2PUSH', '')))
+print(" \"h2_stream_id\" : \"%s\"" % (os.getenv('H2_STREAM_ID', '')))
print("}")
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/htdocs/cgi/mnot164.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/htdocs/cgi/mnot164.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/htdocs/cgi/mnot164.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/htdocs/cgi/mnot164.py Mon Jan 9 07:35:18 2023
@@ -12,10 +12,13 @@ try:
except KeyError:
text="a"
count=77784
-
-
+
+count = int(count)
+
print("Status: 200 OK")
print("Content-Type: text/html")
print()
-sys.stdout.write(text*int(count))
+sys.stdout.flush()
+for _ in range(count):
+ sys.stdout.write(text)
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/mod_h2test/mod_h2test.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/mod_h2test/mod_h2test.c?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/mod_h2test/mod_h2test.c (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/mod_h2test/mod_h2test.c Mon Jan 9 07:35:18 2023
@@ -280,7 +280,7 @@ static int h2test_delay_handler(request_
cleanup:
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
- "delay_handler: request cleanup, r->status=%d, aborted=%d",
+ "delay_handler: request cleanup, r->status=%d, aborte=%d",
r->status, c->aborted);
if (rv == APR_SUCCESS
|| r->status != HTTP_OK
@@ -297,9 +297,8 @@ static int h2test_trailer_handler(reques
apr_bucket *b;
apr_status_t rv;
char buffer[8192];
- int i, chunks = 3;
long l;
- int body_len = 0, tclen;
+ int body_len = 0;
if (strcmp(r->handler, "h2test-trailer")) {
return DECLINED;
@@ -309,7 +308,7 @@ static int h2test_trailer_handler(reques
}
if (r->args) {
- tclen = body_len = (int)apr_atoi64(r->args);
+ body_len = (int)apr_atoi64(r->args);
if (body_len < 0) body_len = 0;
}
@@ -320,6 +319,10 @@ static int h2test_trailer_handler(reques
ap_set_content_length(r, body_len);
ap_set_content_type(r, "application/octet-stream");
+ apr_table_mergen(r->headers_out, "Trailer", "trailer-content-length");
+ apr_table_set(r->trailers_out, "trailer-content-length",
+ apr_psprintf(r->pool, "%d", body_len));
+
bb = apr_brigade_create(r->pool, c->bucket_alloc);
memset(buffer, 0, sizeof(buffer));
while (body_len > 0) {
@@ -332,16 +335,6 @@ static int h2test_trailer_handler(reques
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
"trailer_handler: passed %ld bytes as response body", l);
}
- /* flush it */
- b = apr_bucket_flush_create(c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, b);
- rv = ap_pass_brigade(r->output_filters, bb);
- apr_brigade_cleanup(bb);
- apr_sleep(apr_time_from_msec(500));
- /* set trailers */
- apr_table_mergen(r->headers_out, "Trailer", "trailer-content-length");
- apr_table_set(r->trailers_out, "trailer-content-length",
- apr_psprintf(r->pool, "%d", tclen));
/* we are done */
b = apr_bucket_eos_create(c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
@@ -351,7 +344,7 @@ static int h2test_trailer_handler(reques
cleanup:
ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
- "trailer_handler: request cleanup, r->status=%d, aborted=%d",
+ "trailer_handler: request cleanup, r->status=%d, aborte=%d",
r->status, c->aborted);
if (rv == APR_SUCCESS
|| r->status != HTTP_OK
@@ -361,6 +354,154 @@ cleanup:
return AP_FILTER_ERROR;
}
+static int status_from_str(const char *s, apr_status_t *pstatus)
+{
+ if (!strcmp("timeout", s)) {
+ *pstatus = APR_TIMEUP;
+ return 1;
+ }
+ else if (!strcmp("reset", s)) {
+ *pstatus = APR_ECONNRESET;
+ return 1;
+ }
+ return 0;
+}
+
+static int h2test_error_handler(request_rec *r)
+{
+ conn_rec *c = r->connection;
+ apr_bucket_brigade *bb;
+ apr_bucket *b;
+ apr_status_t rv;
+ char buffer[8192];
+ int i, chunks = 3, error_bucket = 1;
+ long l;
+ apr_time_t delay = 0, body_delay = 0;
+ apr_array_header_t *args = NULL;
+ int http_status = 200;
+ apr_status_t error = APR_SUCCESS, body_error = APR_SUCCESS;
+
+ if (strcmp(r->handler, "h2test-error")) {
+ return DECLINED;
+ }
+ if (r->method_number != M_GET && r->method_number != M_POST) {
+ return DECLINED;
+ }
+
+ if (r->args) {
+ args = apr_cstr_split(r->args, "&", 1, r->pool);
+ for (i = 0; i < args->nelts; ++i) {
+ char *s, *val, *arg = APR_ARRAY_IDX(args, i, char*);
+ s = strchr(arg, '=');
+ if (s) {
+ *s = '\0';
+ val = s + 1;
+ if (!strcmp("status", arg)) {
+ http_status = (int)apr_atoi64(val);
+ if (val > 0) {
+ continue;
+ }
+ }
+ else if (!strcmp("error", arg)) {
+ if (status_from_str(val, &error)) {
+ continue;
+ }
+ }
+ else if (!strcmp("error_bucket", arg)) {
+ error_bucket = (int)apr_atoi64(val);
+ if (val >= 0) {
+ continue;
+ }
+ }
+ else if (!strcmp("body_error", arg)) {
+ if (status_from_str(val, &body_error)) {
+ continue;
+ }
+ }
+ else if (!strcmp("delay", arg)) {
+ rv = duration_parse(&delay, r->args, "s");
+ if (APR_SUCCESS == rv) {
+ continue;
+ }
+ }
+ else if (!strcmp("body_delay", arg)) {
+ rv = duration_parse(&body_delay, r->args, "s");
+ if (APR_SUCCESS == rv) {
+ continue;
+ }
+ }
+ }
+ ap_die(HTTP_BAD_REQUEST, r);
+ return OK;
+ }
+ }
+
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, "error_handler: processing request, %s",
+ r->args? r->args : "(no args)");
+ r->status = http_status;
+ r->clength = -1;
+ r->chunked = 1;
+ apr_table_unset(r->headers_out, "Content-Length");
+ /* Discourage content-encodings */
+ apr_table_unset(r->headers_out, "Content-Encoding");
+ apr_table_setn(r->subprocess_env, "no-brotli", "1");
+ apr_table_setn(r->subprocess_env, "no-gzip", "1");
+
+ ap_set_content_type(r, "application/octet-stream");
+ bb = apr_brigade_create(r->pool, c->bucket_alloc);
+
+ if (delay) {
+ apr_sleep(delay);
+ }
+ if (error != APR_SUCCESS) {
+ return ap_map_http_request_error(error, HTTP_BAD_REQUEST);
+ }
+ /* flush response */
+ b = apr_bucket_flush_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ rv = ap_pass_brigade(r->output_filters, bb);
+ if (APR_SUCCESS != rv) goto cleanup;
+
+ memset(buffer, 'X', sizeof(buffer));
+ l = sizeof(buffer);
+ for (i = 0; i < chunks; ++i) {
+ if (body_delay) {
+ apr_sleep(body_delay);
+ }
+ rv = apr_brigade_write(bb, NULL, NULL, buffer, l);
+ if (APR_SUCCESS != rv) goto cleanup;
+ rv = ap_pass_brigade(r->output_filters, bb);
+ if (APR_SUCCESS != rv) goto cleanup;
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r,
+ "error_handler: passed %ld bytes as response body", l);
+ if (body_error != APR_SUCCESS) {
+ rv = body_error;
+ goto cleanup;
+ }
+ }
+ /* we are done */
+ b = apr_bucket_eos_create(c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ rv = ap_pass_brigade(r->output_filters, bb);
+ apr_brigade_cleanup(bb);
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r, "error_handler: response passed");
+
+cleanup:
+ ap_log_rerror(APLOG_MARK, APLOG_TRACE1, rv, r,
+ "error_handler: request cleanup, r->status=%d, aborted=%d",
+ r->status, c->aborted);
+ if (rv == APR_SUCCESS) {
+ return OK;
+ }
+ if (error_bucket) {
+ http_status = ap_map_http_request_error(rv, HTTP_BAD_REQUEST);
+ b = ap_bucket_error_create(http_status, NULL, r->pool, c->bucket_alloc);
+ APR_BRIGADE_INSERT_TAIL(bb, b);
+ ap_pass_brigade(r->output_filters, bb);
+ }
+ return AP_FILTER_ERROR;
+}
+
/* Install this module into the apache2 infrastructure.
*/
static void h2test_hooks(apr_pool_t *pool)
@@ -381,5 +522,6 @@ static void h2test_hooks(apr_pool_t *poo
ap_hook_handler(h2test_echo_handler, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_handler(h2test_delay_handler, NULL, NULL, APR_HOOK_MIDDLE);
ap_hook_handler(h2test_trailer_handler, NULL, NULL, APR_HOOK_MIDDLE);
+ ap_hook_handler(h2test_error_handler, NULL, NULL, APR_HOOK_MIDDLE);
}
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_001_httpd_alive.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_001_httpd_alive.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_001_httpd_alive.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_001_httpd_alive.py Mon Jan 9 07:35:18 2023
@@ -1,8 +1,9 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestBasicAlive:
@pytest.fixture(autouse=True, scope='class')
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_002_curl_basics.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_002_curl_basics.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_002_curl_basics.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_002_curl_basics.py Mon Jan 9 07:35:18 2023
@@ -1,8 +1,9 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestCurlBasics:
@pytest.fixture(autouse=True, scope='class')
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_003_get.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_003_get.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_003_get.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_003_get.py Mon Jan 9 07:35:18 2023
@@ -1,9 +1,10 @@
import re
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestGet:
@pytest.fixture(autouse=True, scope='class')
@@ -211,3 +212,54 @@ content-type: text/html
assert 1024 == len(r.response["body"])
assert "content-length" in h
assert clen == h["content-length"]
+
+ # use an invalid scheme
+ def test_h2_003_51(self, env):
+ url = env.mkurl("https", "cgi", "/")
+ opt = ["-H:scheme: invalid"]
+ r = env.nghttp().get(url, options=opt)
+ assert r.exit_code == 0, r
+ assert r.response['status'] == 400
+
+ # use an differing scheme, but one that is acceptable
+ def test_h2_003_52(self, env):
+ url = env.mkurl("https", "cgi", "/")
+ opt = ["-H:scheme: http"]
+ r = env.nghttp().get(url, options=opt)
+ assert r.exit_code == 0, r
+ assert r.response['status'] == 200
+
+ # Test that we get a proper `Date` and `Server` headers on responses
+ def test_h2_003_60(self, env):
+ url = env.mkurl("https", "test1", "/index.html")
+ r = env.curl_get(url)
+ assert r.exit_code == 0, r
+ assert r.response['status'] == 200
+ assert 'date' in r.response['header']
+ assert 'server' in r.response['header']
+
+ # lets do some error tests
+ def test_h2_003_70(self, env):
+ url = env.mkurl("https", "cgi", "/h2test/error?status=500")
+ r = env.curl_get(url)
+ assert r.exit_code == 0, r
+ assert r.response['status'] == 500
+ url = env.mkurl("https", "cgi", "/h2test/error?error=timeout")
+ r = env.curl_get(url)
+ assert r.exit_code == 0, r
+ assert r.response['status'] == 408
+
+ # produce an error during response body
+ def test_h2_003_71(self, env, repeat):
+ url = env.mkurl("https", "cgi", "/h2test/error?body_error=timeout")
+ r = env.curl_get(url)
+ assert r.exit_code != 0, f"{r}"
+ url = env.mkurl("https", "cgi", "/h2test/error?body_error=reset")
+ r = env.curl_get(url)
+ assert r.exit_code != 0, f"{r}"
+
+ # produce an error, fail to generate an error bucket
+ def test_h2_003_72(self, env, repeat):
+ url = env.mkurl("https", "cgi", "/h2test/error?body_error=timeout&error_bucket=0")
+ r = env.curl_get(url)
+ assert r.exit_code != 0, f"{r}"
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_004_post.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_004_post.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_004_post.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_004_post.py Mon Jan 9 07:35:18 2023
@@ -8,9 +8,10 @@ import sys
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestPost:
@pytest.fixture(autouse=True, scope='class')
@@ -68,7 +69,7 @@ class TestPost:
("H2_PUSHED", ""),
("H2_PUSHED_ON", ""),
("H2_STREAM_ID", "1"),
- ("H2_STREAM_TAG", r'\d+-1'),
+ ("H2_STREAM_TAG", r'\d+-\d+-1'),
])
def test_h2_004_07(self, env, name, value):
url = env.mkurl("https", "cgi", "/env.py")
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_005_files.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_005_files.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_005_files.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_005_files.py Mon Jan 9 07:35:18 2023
@@ -1,7 +1,7 @@
import os
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
def mk_text_file(fpath: str, lines: int):
@@ -15,6 +15,7 @@ def mk_text_file(fpath: str, lines: int)
fd.write("\n")
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestFiles:
URI_PATHS = []
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_006_assets.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_006_assets.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_006_assets.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_006_assets.py Mon Jan 9 07:35:18 2023
@@ -1,8 +1,9 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestAssets:
@pytest.fixture(autouse=True, scope='class')
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_100_conn_reuse.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_100_conn_reuse.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_100_conn_reuse.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_100_conn_reuse.py Mon Jan 9 07:35:18 2023
@@ -1,8 +1,9 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestConnReuse:
@pytest.fixture(autouse=True, scope='class')
@@ -26,7 +27,7 @@ class TestConnReuse:
def test_h2_100_02(self, env):
url = env.mkurl("https", "cgi", "/hello.py")
hostname = ("cgi-alias.%s" % env.http_tld)
- r = env.curl_get(url, 5, options=[ "-H", "Host:%s" % hostname ])
+ r = env.curl_get(url, 5, options=["-H", f"Host: {hostname}"])
assert r.response["status"] == 200
assert "HTTP/2" == r.response["protocol"]
assert hostname == r.response["json"]["host"]
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_101_ssl_reneg.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_101_ssl_reneg.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_101_ssl_reneg.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_101_ssl_reneg.py Mon Jan 9 07:35:18 2023
@@ -4,6 +4,7 @@ import pytest
from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
@pytest.mark.skipif(H2TestEnv.get_ssl_module() != "mod_ssl", reason="only for mod_ssl")
class TestSslRenegotiation:
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_102_require.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_102_require.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_102_require.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_102_require.py Mon Jan 9 07:35:18 2023
@@ -1,8 +1,9 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestRequire:
@pytest.fixture(autouse=True, scope='class')
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_103_upgrade.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_103_upgrade.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_103_upgrade.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_103_upgrade.py Mon Jan 9 07:35:18 2023
@@ -1,8 +1,9 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestUpgrade:
@pytest.fixture(autouse=True, scope='class')
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_104_padding.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_104_padding.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_104_padding.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_104_padding.py Mon Jan 9 07:35:18 2023
@@ -1,6 +1,6 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
def frame_padding(payload, padbits):
@@ -8,6 +8,7 @@ def frame_padding(payload, padbits):
return ((payload + 9 + mask) & ~mask) - (payload + 9)
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestPadding:
@pytest.fixture(autouse=True, scope='class')
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_105_timeout.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_105_timeout.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_105_timeout.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_105_timeout.py Mon Jan 9 07:35:18 2023
@@ -111,40 +111,39 @@ class TestTimeout:
assert piper.exitcode == 0
assert len("".join(stdout)) == 3 * 8192
- @pytest.mark.skip(reason="only in 2.5.x")
def test_h2_105_11(self, env):
# short connection timeout, longer stream delay
- # receiving the first response chunk, then timeout
+ # connection timeout must not abort ongoing streams
conf = H2Conf(env)
conf.add_vhost_cgi()
conf.add("Timeout 1")
conf.install()
assert env.apache_restart() == 0
- url = env.mkurl("https", "cgi", "/h2test/delay?5")
+ url = env.mkurl("https", "cgi", "/h2test/delay?1200ms")
piper = CurlPiper(env=env, url=url)
piper.start()
stdout, stderr = piper.close()
- assert len("".join(stdout)) == 8192
+ assert len("".join(stdout)) == 3 * 8192
- @pytest.mark.skip(reason="only in 2.5.x")
def test_h2_105_12(self, env):
# long connection timeout, short stream timeout
# sending a slow POST
- conf = H2Conf(env)
- conf.add_vhost_cgi()
- conf.add("Timeout 10")
- conf.add("H2StreamTimeout 1")
- conf.install()
- assert env.apache_restart() == 0
- url = env.mkurl("https", "cgi", "/h2test/delay?5")
- piper = CurlPiper(env=env, url=url)
- piper.start()
- for _ in range(3):
- time.sleep(2)
- try:
- piper.send("0123456789\n")
- except BrokenPipeError:
- break
- piper.close()
- assert piper.response
- assert piper.response['status'] == 408
+ if env.httpd_is_at_least("2.5.0"):
+ conf = H2Conf(env)
+ conf.add_vhost_cgi()
+ conf.add("Timeout 10")
+ conf.add("H2StreamTimeout 1")
+ conf.install()
+ assert env.apache_restart() == 0
+ url = env.mkurl("https", "cgi", "/h2test/delay?5")
+ piper = CurlPiper(env=env, url=url)
+ piper.start()
+ for _ in range(3):
+ time.sleep(2)
+ try:
+ piper.send("0123456789\n")
+ except BrokenPipeError:
+ break
+ piper.close()
+ assert piper.response
+ assert piper.response['status'] == 408, f"{piper.response}"
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_106_shutdown.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_106_shutdown.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_106_shutdown.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_106_shutdown.py Mon Jan 9 07:35:18 2023
@@ -7,10 +7,11 @@ from threading import Thread
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
from pyhttpd.result import ExecResult
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestShutdown:
@pytest.fixture(autouse=True, scope='class')
Modified: httpd/httpd/branches/2.4.x/test/modules/http2/test_200_header_invalid.py
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/test/modules/http2/test_200_header_invalid.py?rev=1906475&r1=1906474&r2=1906475&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/test/modules/http2/test_200_header_invalid.py (original)
+++ httpd/httpd/branches/2.4.x/test/modules/http2/test_200_header_invalid.py Mon Jan 9 07:35:18 2023
@@ -1,8 +1,9 @@
import pytest
-from .env import H2Conf
+from .env import H2Conf, H2TestEnv
+@pytest.mark.skipif(condition=H2TestEnv.is_unsupported, reason="mod_http2 not supported here")
class TestInvalidHeaders:
@pytest.fixture(autouse=True, scope='class')
@@ -86,7 +87,8 @@ class TestInvalidHeaders:
def test_h2_200_12(self, env):
url = env.mkurl("https", "cgi", "/")
opt = []
- for i in range(98): # curl sends 2 headers itself (user-agent and accept)
+ # curl sends 3 headers itself (user-agent, accept, and our AP-Test-Name)
+ for i in range(97):
opt += ["-H", "x: 1"]
r = env.curl_get(url, options=opt)
assert r.response["status"] == 200
@@ -98,8 +100,9 @@ class TestInvalidHeaders:
def test_h2_200_13(self, env):
url = env.mkurl("https", "cgi", "/")
opt = []
- for i in range(98): # curl sends 2 headers itself (user-agent and accept)
- opt += ["-H", "x{0}: 1".format(i)]
+ # curl sends 3 headers itself (user-agent, accept, and our AP-Test-Name)
+ for i in range(97):
+ opt += ["-H", f"x{i}: 1"]
r = env.curl_get(url, options=opt)
assert r.response["status"] == 200
r = env.curl_get(url, options=(opt + ["-H", "y: 2"]))