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;

Reply via email to