I enhanced my worker MPM and apr_pool_t optimization
patch by adding "recycling" of transaction pools so that
we can avoid the mutex overhead associated with destroying
pools.
With this patch, the fdqueue pop function allows a worker
thread to return its previous ptrans pool to the listener
thread. The fdqueue object caches the recycled pools internally
and returns them to the listener, one at a time, when the
listener pushes the next connection onto the queue.
The motivation for exchanging recycled pools as a side-effect
of the fdqueue push and pop operations is that we get to take
advantage of the mutex locking that's done there already, so
no extra locking is needed.
--Brian
Index: server/mpm/worker/fdqueue.h
===================================================================
RCS file: /home/cvs/httpd-2.0/server/mpm/worker/fdqueue.h,v
retrieving revision 1.9
diff -u -r1.9 fdqueue.h
--- server/mpm/worker/fdqueue.h 2001/10/17 16:29:37 1.9
+++ server/mpm/worker/fdqueue.h 2001/12/01 09:15:44
@@ -63,6 +63,7 @@
#if APR_HAVE_UNISTD_H
#include <unistd.h>
#endif
+#include <apr_pools.h>
#include <apr_thread_mutex.h>
#include <apr_thread_cond.h>
#include <sys/types.h>
@@ -89,13 +90,19 @@
apr_thread_cond_t *not_empty;
apr_thread_cond_t *not_full;
int cancel_state;
+ apr_pool_t **pool_cache;
+ int pool_cache_size;
+ int pool_cache_count;
};
typedef struct fd_queue_t fd_queue_t;
/* FIXME: APRize these -- return values should be apr_status_t */
-int ap_queue_init(fd_queue_t *queue, int queue_capacity, apr_pool_t *a);
-int ap_queue_push(fd_queue_t *queue, apr_socket_t *sd, apr_pool_t *p);
-apr_status_t ap_queue_pop(fd_queue_t *queue, apr_socket_t **sd, apr_pool_t **p);
+int ap_queue_init(fd_queue_t *queue, int queue_capacity, apr_pool_t *a,
+ int pool_cache_size);
+int ap_queue_push(fd_queue_t *queue, apr_socket_t *sd, apr_pool_t *p,
+ apr_pool_t **recycled_pool);
+apr_status_t ap_queue_pop(fd_queue_t *queue, apr_socket_t **sd, apr_pool_t **p,
+ apr_pool_t *recycled);
apr_status_t ap_queue_interrupt_all(fd_queue_t *queue);
#endif /* FDQUEUE_H */
Index: server/mpm/worker/fdqueue.c
===================================================================
RCS file: /home/cvs/httpd-2.0/server/mpm/worker/fdqueue.c,v
retrieving revision 1.9
diff -u -r1.9 fdqueue.c
--- server/mpm/worker/fdqueue.c 2001/10/17 16:29:36 1.9
+++ server/mpm/worker/fdqueue.c 2001/12/01 09:15:44
@@ -91,7 +91,8 @@
/**
* Initialize the fd_queue_t.
*/
-int ap_queue_init(fd_queue_t *queue, int queue_capacity, apr_pool_t *a)
+int ap_queue_init(fd_queue_t *queue, int queue_capacity, apr_pool_t *a,
+ int pool_cache_size)
{
int i;
@@ -112,6 +113,11 @@
for (i = 0; i < queue_capacity; ++i)
queue->data[i].sd = NULL;
+ /* Build a cache to hold "recycled" pools */
+ queue->pool_cache_size = pool_cache_size;
+ queue->pool_cache_count = 0;
+ queue->pool_cache = apr_palloc(a, pool_cache_size * sizeof(apr_pool_t *));
+
apr_pool_cleanup_register(a, queue, ap_queue_destroy, apr_pool_cleanup_null);
return FD_QUEUE_SUCCESS;
@@ -122,7 +128,8 @@
* the push operation has completed, it signals other threads waiting
* in apr_queue_pop() that they may continue consuming sockets.
*/
-int ap_queue_push(fd_queue_t *queue, apr_socket_t *sd, apr_pool_t *p)
+int ap_queue_push(fd_queue_t *queue, apr_socket_t *sd, apr_pool_t *p,
+ apr_pool_t **recycled_pool)
{
fd_queue_elem_t *elem;
@@ -138,6 +145,16 @@
elem->sd = sd;
elem->p = p;
+ if (recycled_pool) {
+ if (queue->pool_cache_count == 0) {
+ *recycled_pool = NULL;
+ }
+ else {
+ queue->pool_cache_count--;
+ *recycled_pool = queue->pool_cache[queue->pool_cache_count];
+ }
+ }
+
apr_thread_cond_signal(queue->not_empty);
if (apr_thread_mutex_unlock(queue->one_big_mutex) != APR_SUCCESS) {
@@ -153,7 +170,8 @@
* Once retrieved, the socket is placed into the address specified by
* 'sd'.
*/
-apr_status_t ap_queue_pop(fd_queue_t *queue, apr_socket_t **sd, apr_pool_t **p)
+apr_status_t ap_queue_pop(fd_queue_t *queue, apr_socket_t **sd, apr_pool_t **p,
+ apr_pool_t *recycled)
{
fd_queue_elem_t *elem;
@@ -172,6 +190,16 @@
return FD_QUEUE_EINTR;
}
}
+
+ if (recycled) {
+ if (queue->pool_cache_count < queue->pool_cache_size) {
+ queue->pool_cache[queue->pool_cache_count] = recycled;
+ queue->pool_cache_count++;
+ }
+ else {
+ apr_pool_destroy(recycled);
+ }
+ }
elem = &queue->data[--queue->tail];
*sd = elem->sd;
Index: server/mpm/worker/worker.c
===================================================================
RCS file: /home/cvs/httpd-2.0/server/mpm/worker/worker.c,v
retrieving revision 1.43
diff -u -r1.43 worker.c
--- server/mpm/worker/worker.c 2001/11/22 05:13:29 1.43
+++ server/mpm/worker/worker.c 2001/12/01 09:15:46
@@ -563,7 +563,7 @@
int thread_slot = ti->tid;
apr_pool_t *tpool = apr_thread_pool_get(thd);
void *csd = NULL;
- apr_pool_t *ptrans; /* Pool for per-transaction stuff */
+ apr_pool_t *ptrans = NULL; /* Pool for per-transaction stuff */
int n;
apr_pollfd_t *pollset;
apr_status_t rv;
@@ -639,8 +639,12 @@
}
got_fd:
if (!workers_may_exit) {
- /* create a new transaction pool for each accepted socket */
- apr_pool_create(&ptrans, tpool);
+ if (!ptrans) {
+ /* create a new transaction pool for each accepted socket */
+ apr_pool_create(&ptrans, tpool);
+ apr_pool_set_options(ptrans, APR_POOL_OPT_SINGLE_THREADED |
+ APR_POOL_OPT_CACHE_FREELIST);
+ }
rv = lr->accept_func(&csd, lr, ptrans);
@@ -655,7 +659,7 @@
signal_workers();
}
if (csd != NULL) {
- rv = ap_queue_push(worker_queue, csd, ptrans);
+ rv = ap_queue_push(worker_queue, csd, ptrans, &ptrans);
if (rv) {
/* trash the connection; we couldn't queue the connected
* socket to a worker
@@ -697,7 +701,7 @@
int process_slot = ti->pid;
int thread_slot = ti->tid;
apr_socket_t *csd = NULL;
- apr_pool_t *ptrans; /* Pool for per-transaction stuff */
+ apr_pool_t *ptrans = NULL; /* Pool for per-transaction stuff */
apr_status_t rv;
free(ti);
@@ -705,7 +709,7 @@
ap_update_child_status(process_slot, thread_slot, SERVER_STARTING, NULL);
while (!workers_may_exit) {
ap_update_child_status(process_slot, thread_slot, SERVER_READY, NULL);
- rv = ap_queue_pop(worker_queue, &csd, &ptrans);
+ rv = ap_queue_pop(worker_queue, &csd, &ptrans, ptrans);
/* We get FD_QUEUE_EINTR whenever ap_queue_pop() has been interrupted
* from an explicit call to ap_queue_interrupt_all(). This allows
* us to unblock threads stuck in ap_queue_pop() when a shutdown
@@ -715,7 +719,7 @@
}
process_socket(ptrans, csd, process_slot, thread_slot);
requests_this_child--; /* FIXME: should be synchronized - aaron */
- apr_pool_destroy(ptrans);
+ apr_pool_clear(ptrans);
}
ap_update_child_status(process_slot, thread_slot,
@@ -754,7 +758,8 @@
/* We must create the fd queues before we start up the listener
* and worker threads. */
worker_queue = apr_pcalloc(pchild, sizeof(*worker_queue));
- ap_queue_init(worker_queue, ap_threads_per_child, pchild);
+ ap_queue_init(worker_queue, ap_threads_per_child, pchild,
+ ap_threads_per_child + 1);
my_info = (proc_info *)malloc(sizeof(proc_info));
my_info->pid = my_child_num;
Index: server/mpm/prefork/prefork.c
===================================================================
RCS file: /home/cvs/httpd-2.0/server/mpm/prefork/prefork.c,v
retrieving revision 1.223
diff -u -r1.223 prefork.c
--- server/mpm/prefork/prefork.c 2001/11/29 04:06:05 1.223
+++ server/mpm/prefork/prefork.c 2001/12/01 09:15:46
@@ -559,7 +559,8 @@
* we can have cleanups occur when the child exits.
*/
apr_pool_create(&pchild, pconf);
-
+ apr_pool_set_options(pchild, APR_POOL_OPT_SINGLE_THREADED |
+ APR_POOL_OPT_CACHE_FREELIST);
apr_pool_create(&ptrans, pchild);
/* needs to be done before we switch UIDs so we have permissions */
Index: srclib/apr/include/apr_pools.h
===================================================================
RCS file: /home/cvspublic/apr/include/apr_pools.h,v
retrieving revision 1.63
diff -u -r1.63 apr_pools.h
--- srclib/apr/include/apr_pools.h 2001/11/09 17:50:48 1.63
+++ srclib/apr/include/apr_pools.h 2001/12/01 09:15:47
@@ -363,6 +363,32 @@
apr_pool_t *pparent,
int (*apr_abort)(int retcode));
+/* Options for use with apr_pool_set_options: */
+
+/** Optimize a pool for use with a single thread only */
+#define APR_POOL_OPT_SINGLE_THREADED 0x1
+/** Maintain a cache of free blocks in a pool for use in creating subpools */
+#define APR_POOL_OPT_CACHE_FREELIST 0x2
+
+/**
+ * Set performance tuning options for a pool
+ * @param p The pool
+ * @param flags The APR_POOL_OPT_* flags to enable for the pool,
+ * OR'ed together
+ * @remark Options set with this function are inherited by subsequently
+ * created child pools, although the children can override the
+ * settings.
+ */
+APR_DECLARE(void) apr_pool_set_options(apr_pool_t *p, apr_uint32_t flags);
+
+/**
+ * Retrieve the performance tuning options for a pool
+ * @param p The pool
+ * @return The APR_POOL_OPT_* flags that are enabled for the pool,
+ * OR'ed together
+ */
+APR_DECLARE(apr_uint32_t) apr_pool_get_options(const apr_pool_t *p);
+
/**
* Register a function to be called when a pool is cleared or destroyed
* @param p The pool register the cleanup with
Index: srclib/apr/memory/unix/apr_pools.c
===================================================================
RCS file: /home/cvspublic/apr/memory/unix/apr_pools.c,v
retrieving revision 1.117
diff -u -r1.117 apr_pools.c
--- srclib/apr/memory/unix/apr_pools.c 2001/11/23 16:47:52 1.117
+++ srclib/apr/memory/unix/apr_pools.c 2001/12/01 09:15:48
@@ -67,7 +67,7 @@
#include "apr_general.h"
#include "apr_pools.h"
#include "apr_lib.h"
-#include "apr_lock.h"
+#include "apr_thread_mutex.h"
#include "apr_hash.h"
#if APR_HAVE_STDIO_H
@@ -207,6 +207,15 @@
int (*apr_abort)(int retcode);
/** A place to hold user data associated with this pool */
struct apr_hash_t *prog_data;
+ /** Tuning options for this pool */
+ int flags;
+ /** Optional cache of free blocks to use when allocating child pools */
+ union block_hdr *free_list_cache;
+ /** If non-null, points to the pool whose free list should be used
+ * when allocating blocks for this pool (will be either this pool
+ * itself or an ancestor thereof)
+ */
+ struct apr_pool_t *free_list_cache_owner;
};
@@ -253,7 +262,7 @@
static union block_hdr *block_freelist = NULL;
#if APR_HAS_THREADS
-static apr_lock_t *alloc_mutex;
+static apr_thread_mutex_t *alloc_mutex;
#endif
#ifdef APR_POOL_DEBUG
@@ -477,7 +486,8 @@
/* Free a chain of blocks --- must be called with alarms blocked. */
-static void free_blocks(union block_hdr *blok)
+static void free_blocks(union block_hdr *blok, union block_hdr **free_list,
+ int use_lock)
{
#ifdef ALLOC_USE_MALLOC
union block_hdr *next;
@@ -492,6 +502,10 @@
unsigned num_blocks;
#endif /* ALLOC_STATS */
+#if APR_HAS_THREADS
+ int lock_needed = (use_lock && alloc_mutex);
+#endif /* APR_HAS_THREADS */
+
/*
* First, put new blocks at the head of the free list ---
* we'll eventually bash the 'next' pointer of the last block
@@ -505,12 +519,12 @@
}
#if APR_HAS_THREADS
- if (alloc_mutex) {
- apr_lock_acquire(alloc_mutex);
+ if (lock_needed) {
+ apr_thread_mutex_lock(alloc_mutex);
}
#endif
- old_free_list = block_freelist;
- block_freelist = blok;
+ old_free_list = *free_list;
+ *free_list = blok;
/*
* Next, adjust first_avail pointers of each block --- have to do it
@@ -557,8 +571,8 @@
#endif /* ALLOC_STATS */
#if APR_HAS_THREADS
- if (alloc_mutex) {
- apr_lock_release(alloc_mutex);
+ if (lock_needed) {
+ apr_thread_mutex_unlock(alloc_mutex);
}
#endif /* APR_HAS_THREADS */
#endif /* ALLOC_USE_MALLOC */
@@ -568,10 +582,11 @@
* Get a new block, from our own free list if possible, from the system
* if necessary. Must be called with alarms blocked.
*/
-static union block_hdr *new_block(apr_size_t min_size, apr_abortfunc_t abortfunc)
+static union block_hdr *new_block(union block_hdr **free_list,
+ apr_size_t min_size, apr_abortfunc_t abortfunc)
{
- union block_hdr **lastptr = &block_freelist;
- union block_hdr *blok = block_freelist;
+ union block_hdr **lastptr = free_list;
+ union block_hdr *blok = *free_list;
/* First, see if we have anything of the required size
* on the free list...
@@ -648,15 +663,48 @@
{
union block_hdr *blok;
apr_pool_t *new_pool;
+ union block_hdr **free_list;
+#if APR_HAS_THREADS
+ int lock_needed;
+ int have_lock = 0;
+#endif /* APR_HAS_THREADS */
+ /* If the parent pool has its own free list, use it if it's non-empty.
+ * Otherwise, use the global free list.
+ */
+ if (parent && parent->free_list_cache_owner) {
+ free_list = &(parent->free_list_cache_owner->free_list_cache);
+#if APR_HAS_THREADS
+ lock_needed = (alloc_mutex &&
+ (!(parent->flags & APR_POOL_OPT_SINGLE_THREADED) ||
+ !(parent->free_list_cache_owner->flags &
+ APR_POOL_OPT_SINGLE_THREADED)));
+ if (lock_needed) {
+ apr_thread_mutex_lock(alloc_mutex);
+ have_lock = 1;
+ }
+#endif /* APR_HAS_THREADS */
+ if (!parent->free_list_cache_owner->free_list_cache) {
+ free_list = &block_freelist;
+#if APR_HAS_THREADS
+ lock_needed = (alloc_mutex != NULL);
+#endif /* APR_HAS_THREADS */
+ }
+ }
+ else {
+ free_list = &block_freelist;
+#if APR_HAS_THREADS
+ lock_needed = (alloc_mutex != NULL);
+#endif /* APR_HAS_THREADS */
+ }
#if APR_HAS_THREADS
- if (alloc_mutex) {
- apr_lock_acquire(alloc_mutex);
+ if (lock_needed && !have_lock) {
+ apr_thread_mutex_lock(alloc_mutex);
}
#endif
- blok = new_block(POOL_HDR_BYTES, abortfunc);
+ blok = new_block(free_list, POOL_HDR_BYTES, abortfunc);
new_pool = (apr_pool_t *) blok->h.first_avail;
blok->h.first_avail += POOL_HDR_BYTES;
#ifdef APR_POOL_DEBUG
@@ -674,11 +722,13 @@
new_pool->sub_next->sub_prev = new_pool;
}
parent->sub_pools = new_pool;
+ new_pool->flags = parent->flags;
+ new_pool->free_list_cache_owner = parent->free_list_cache_owner;
}
#if APR_HAS_THREADS
- if (alloc_mutex) {
- apr_lock_release(alloc_mutex);
+ if (lock_needed) {
+ apr_thread_mutex_unlock(alloc_mutex);
}
#endif
@@ -752,6 +802,29 @@
return pool->parent;
}
+APR_DECLARE(void) apr_pool_set_options(apr_pool_t *p, apr_uint32_t flags)
+{
+ p->flags = flags;
+ if (flags & APR_POOL_OPT_CACHE_FREELIST) {
+ if (!p->parent || !p->parent->free_list_cache_owner) {
+ p->free_list_cache_owner = p;
+ }
+ }
+ else {
+ if (p->parent) {
+ p->free_list_cache_owner = p->parent->free_list_cache_owner;
+ }
+ else {
+ p->free_list_cache_owner = NULL;
+ }
+ }
+}
+
+APR_DECLARE(apr_uint32_t) apr_pool_get_options(const apr_pool_t *p)
+{
+ return p->flags;
+}
+
/*****************************************************************
*
* Managing generic cleanups.
@@ -887,8 +960,8 @@
stack_var_init(&s);
#endif
#if APR_HAS_THREADS
- status = apr_lock_create(&alloc_mutex, APR_MUTEX, APR_INTRAPROCESS,
- NULL, globalp);
+ status = apr_thread_mutex_create(&alloc_mutex, APR_THREAD_MUTEX_DEFAULT,
+ globalp);
if (status != APR_SUCCESS) {
return status;
}
@@ -905,7 +978,7 @@
APR_DECLARE(void) apr_pool_alloc_term(apr_pool_t *globalp)
{
#if APR_HAS_THREADS
- apr_lock_destroy(alloc_mutex);
+ apr_thread_mutex_destroy(alloc_mutex);
alloc_mutex = NULL;
#endif
apr_pool_destroy(globalp);
@@ -954,8 +1027,18 @@
/* free the pool's blocks, *except* for the first one. the actual pool
structure is contained in the first block. this also gives us some
ready memory for reallocating within this pool. */
- free_blocks(a->first->h.next);
- a->first->h.next = NULL;
+ if (a->first->h.next) {
+ if (a->free_list_cache_owner) {
+ free_blocks(a->first->h.next,
+ &(a->free_list_cache_owner->free_list_cache),
+ !(a->free_list_cache_owner->flags &
+ APR_POOL_OPT_SINGLE_THREADED));
+ }
+ else {
+ free_blocks(a->first->h.next, &block_freelist, 1);
+ }
+ a->first->h.next = NULL;
+ }
/* this was allocated in self, or a subpool of self. it simply
disappears, so forget the hash table. */
@@ -994,14 +1077,15 @@
/* toss everything in the pool. */
apr_pool_clear(a);
-#if APR_HAS_THREADS
- if (alloc_mutex) {
- apr_lock_acquire(alloc_mutex);
- }
-#endif
-
/* detach this pool from its parent. */
if (a->parent) {
+#if APR_HAS_THREADS
+ int lock_required = (alloc_mutex &&
+ !(a->parent->flags & APR_POOL_OPT_SINGLE_THREADED));
+ if (lock_required) {
+ apr_thread_mutex_lock(alloc_mutex);
+ }
+#endif
if (a->parent->sub_pools == a) {
a->parent->sub_pools = a->sub_next;
}
@@ -1011,13 +1095,16 @@
if (a->sub_next) {
a->sub_next->sub_prev = a->sub_prev;
}
+#if APR_HAS_THREADS
+ if (lock_required) {
+ apr_thread_mutex_unlock(alloc_mutex);
+ }
+#endif
}
-#if APR_HAS_THREADS
- if (alloc_mutex) {
- apr_lock_release(alloc_mutex);
+ if (a->free_list_cache) {
+ free_blocks(a->free_list_cache, &block_freelist, 1);
}
-#endif
/* freeing the first block will include the pool structure. to prevent
a double call to apr_pool_destroy, we want to fill a NULL into
@@ -1031,7 +1118,15 @@
*/
blok = a->first;
a->first = NULL;
- free_blocks(blok);
+
+ if (a->free_list_cache_owner && (a->free_list_cache_owner != a)) {
+ free_blocks(blok, &(a->free_list_cache_owner->free_list_cache),
+ !(a->free_list_cache_owner->flags &
+ APR_POOL_OPT_SINGLE_THREADED));
+ }
+ else {
+ free_blocks(blok, &block_freelist, 1);
+ }
}
@@ -1231,11 +1326,11 @@
#if APR_HAS_THREADS
if (alloc_mutex) {
- apr_lock_acquire(alloc_mutex);
+ apr_thread_mutex_lock(alloc_mutex);
}
#endif
- blok = new_block(size, a->apr_abort);
+ blok = new_block(&block_freelist, size, a->apr_abort);
a->last->h.next = blok;
a->last = blok;
#ifdef APR_POOL_DEBUG
@@ -1244,7 +1339,7 @@
#if APR_HAS_THREADS
if (alloc_mutex) {
- apr_lock_release(alloc_mutex);
+ apr_thread_mutex_unlock(alloc_mutex);
}
#endif
@@ -1370,11 +1465,11 @@
/* must try another blok */
#if APR_HAS_THREADS
- apr_lock_acquire(alloc_mutex);
+ apr_thread_mutex_lock(alloc_mutex);
#endif
- nblok = new_block(2 * cur_len, NULL);
+ nblok = new_block(&block_freelist, 2 * cur_len, NULL);
#if APR_HAS_THREADS
- apr_lock_release(alloc_mutex);
+ apr_thread_mutex_unlock(alloc_mutex);
#endif
memcpy(nblok->h.first_avail, blok->h.first_avail, cur_len);
ps->vbuff.curpos = nblok->h.first_avail + cur_len;
@@ -1385,12 +1480,12 @@
if (ps->got_a_new_block) {
debug_fill(blok->h.first_avail, blok->h.endp - blok->h.first_avail);
#if APR_HAS_THREADS
- apr_lock_acquire(alloc_mutex);
+ apr_thread_mutex_lock(alloc_mutex);
#endif
blok->h.next = block_freelist;
block_freelist = blok;
#if APR_HAS_THREADS
- apr_lock_release(alloc_mutex);
+ apr_thread_mutex_unlock(alloc_mutex);
#endif
}
ps->blok = nblok;