]> granicus.if.org Git - apache/commitdiff
* modules/ssl/ssl_scache_shmcb.c: Rewrite of shmcb session cache to
authorJoe Orton <jorton@apache.org>
Wed, 3 May 2006 13:16:57 +0000 (13:16 +0000)
committerJoe Orton <jorton@apache.org>
Wed, 3 May 2006 13:16:57 +0000 (13:16 +0000)
reduce complexity and ensure all accesses within shm segment are
always aligned correctly.

Submitted by: Geoff Thorpe <geoff geoffthorpe.net>

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@399291 13f79535-47bb-0310-9956-ffa450edef68

modules/ssl/ssl_scache_shmcb.c

index 8746683ef59e306676f551438d83669617bba2fe..98fadd7c90c465de0a98276d3a05574989d64af1 100644 (file)
 
 #include "ssl_private.h"
 
-/*
+/* 
  * This shared memory based SSL session cache implementation was
  * originally written by Geoff Thorpe <geoff geoffthorpe.net> for C2Net
  * Europe as a contribution to Ralf Engelschall's mod_ssl project.
- */
-
-/*
- * The shared-memory segment header can be cast to and from the
- * SHMCBHeader type, all other structures need to be initialised by
- * utility functions.
- *
- * The "header" looks like this;
- *
- * data applying to the overall structure:
- * - division_offset (unsigned int):
- *   how far into the shared memory segment the first division is.
- * - division_size (unsigned int):
- *   how many bytes each division occupies.
- *   (NB: This includes the queue and the cache)
- * - division_mask (unsigned char):
- *   the "mask" in the next line. Add one to this,
- *   and that's the number of divisions.
- *
- * data applying to within each division:
- * - queue_size (unsigned int):
- *   how big each "queue" is. NB: The queue is the first block in each
- *   division and is followed immediately by the cache itself so so
- *   there's no cache_offset value.
- *
- * data applying to within each queue:
- * - index_num (unsigned char):
- *   how many indexes in each cache's queue
- * - index_offset (unsigned char):
- *   how far into the queue the first index is.
- * - index_size:
- *   how big each index is.
- *
- * data applying to within each cache:
- * - cache_data_offset (unsigned int):
- *   how far into the cache the session-data array is stored.
- * - cache_data_size (unsigned int):
- *   how big each cache's data block is.
- *
- * statistics data (this will eventually be per-division but right now
- * there's only one mutex):
- * - stores (unsigned long):
- *   how many stores have been performed in the cache.
- * - expiries (unsigned long):
- *   how many session have been expired from the cache.
- * - scrolled (unsigned long):
- *   how many sessions have been scrolled out of full cache during a
- *   "store" operation. This is different to the "removes" stats as
- *   they are requested by mod_ssl/Apache, these are done because of
- *   cache logistics. (NB: Also, this value should be deducible from
- *   the others if my code has no bugs, but I count it anyway - plus
- *   it helps debugging :-).
- * - retrieves_hit (unsigned long):
- *   how many session-retrieves have succeeded.
- * - retrieves_miss (unsigned long):
- *   how many session-retrieves have failed.
- * - removes_hit (unsigned long):
- * - removes_miss (unsigned long):
- *
- * Following immediately after the header is an array of "divisions".
- * Each division is simply a "queue" immediately followed by its
- * corresponding "cache". Each division handles some pre-defined band
- * of sessions by using the "division_mask" in the header. Eg. if
- * division_mask=0x1f then there are 32 divisions, the first of which
- * will store sessions whose least-significant 5 bits are 0, the second
- * stores session whose LS 5 bits equal 1, etc. A queue is an indexing
- * structure referring to its corresponding cache.
- *
- * A "queue" looks like this;
  *
- * - first_pos (unsigned int):
- *   the location within the array of indexes where the virtual
- *   "left-hand-edge" of the cyclic buffer is.
- * - pos_count (unsigned int):
- *   the number of indexes occupied from first_pos onwards.
- *
- * ...followed by an array of indexes, each of which can be
- * memcpy'd to and from an SHMCBIndex, and look like this;
- *
- * - expires (time_t):
- *   the time() value at which this session expires.
- * - offset (unsigned int):
- *   the offset within the cache data block where the corresponding
- *   session is stored.
- * - s_id2 (unsigned char):
- *   the second byte of the session_id, stored as an optimisation to
- *   reduce the number of d2i_SSL_SESSION calls that are made when doing
- *   a lookup.
- * - removed (unsigned char):
- *   a byte used to indicate whether a session has been "passively"
- *   removed. Ie. it is still in the cache but is to be disregarded by
- *   any "retrieve" operation.
- *
- * A "cache" looks like this;
- *
- * - first_pos (unsigned int):
- *   the location within the data block where the virtual
- *   "left-hand-edge" of the cyclic buffer is.
- * - pos_count (unsigned int):
- *   the number of bytes used in the data block from first_pos onwards.
- *
- * ...followed by the data block in which actual DER-encoded SSL
- * sessions are stored.
+ * Since rewritten by GT to not use alignment-fudging memcpys and reduce
+ * complexity.
  */
 
 /*
- * Header - can be memcpy'd to and from the front of the shared
- * memory segment. NB: The first copy (commented out) has the
- * elements in a meaningful order, but due to data-alignment
- * braindeadness, the second (uncommented) copy has the types grouped
- * so as to decrease "struct-bloat". sigh.
+ * Header structure - the start of the shared-mem segment
  */
 typedef struct {
-    unsigned long num_stores;
-    unsigned long num_expiries;
-    unsigned long num_scrolled;
-    unsigned long num_retrieves_hit;
-    unsigned long num_retrieves_miss;
-    unsigned long num_removes_hit;
-    unsigned long num_removes_miss;
-    unsigned int division_offset;
-    unsigned int division_size;
-    unsigned int queue_size;
-    unsigned int cache_data_offset;
-    unsigned int cache_data_size;
-    unsigned char division_mask;
+    /* Stats for cache operations */
+    unsigned long stat_stores;
+    unsigned long stat_expiries;
+    unsigned long stat_scrolled;
+    unsigned long stat_retrieves_hit;
+    unsigned long stat_retrieves_miss;
+    unsigned long stat_removes_hit;
+    unsigned long stat_removes_miss;
+    /* Number of subcaches */
+    unsigned int subcache_num;
+    /* How many indexes each subcache's queue has */
     unsigned int index_num;
-    unsigned int index_offset;
-    unsigned int index_size;
+    /* How large each subcache is, including the queue and data */
+    unsigned int subcache_size;
+    /* How far into each subcache the data area is (optimisation) */
+    unsigned int subcache_data_offset;
+    /* How large the data area in each subcache is (optimisation) */
+    unsigned int subcache_data_size;
 } SHMCBHeader;
 
-/*
- * Index - can be memcpy'd to and from an index inside each
- * queue's index array.
+/* 
+ * Subcache structure - the start of each subcache, followed by
+ * indexes then data
  */
 typedef struct {
+    /* The start position and length of the cyclic buffer of indexes */
+    unsigned int idx_pos, idx_used;
+    /* Same for the data area */
+    unsigned int data_pos, data_used;
+} SHMCBSubcache;
+
+/* 
+ * Index structure - each subcache has an array of these
+ */
+typedef struct {
+    /* absolute time this entry expires */
     time_t expires;
-    unsigned int offset;
+    /* location within the subcache's data area */
+    unsigned int data_pos;
+    /* size (most logic ignores this, we keep it only to minimise memcpy) */
+    unsigned int data_used;
+    /* Optimisation to prevent ASN decoding unless a match is likely */
     unsigned char s_id2;
+    /* Used to mark explicitly-removed sessions */
     unsigned char removed;
 } SHMCBIndex;
 
-/*
- * Queue - must be populated by a call to shmcb_get_division
- * and the structure's pointers are used for updating (ie.
- * the structure doesn't need any "set" to update values).
- */
-typedef struct {
-    SHMCBHeader *header;
-    unsigned int *first_pos;
-    unsigned int *pos_count;
-    SHMCBIndex *indexes;
-} SHMCBQueue;
-
-/*
- * Cache - same comment as for Queue. 'Queue's are in a 1-1
- * correspondance with 'Cache's and are usually carried round
- * in a pair, they are only seperated for clarity.
- */
-typedef struct {
-    SHMCBHeader *header;
-    unsigned int *first_pos;
-    unsigned int *pos_count;
-    unsigned char *data;
-} SHMCBCache;
+/* This macro takes a pointer to the header and a zero-based index and returns
+ * a pointer to the corresponding subcache. */
+#define SHMCB_SUBCACHE(pHeader, num) \
+                (SHMCBSubcache *)(((unsigned char *)(pHeader)) + \
+                        sizeof(SHMCBHeader) + \
+                        (num) * ((pHeader)->subcache_size))
+
+/* This macro takes a pointer to the header and a session id and returns a
+ * pointer to the corresponding subcache. */
+#define SHMCB_MASK(pHeader, id) \
+                SHMCB_SUBCACHE((pHeader), *(id) & ((pHeader)->subcache_num - 1))
+
+/* This macro takes the same params as the last, generating two outputs for use
+ * in ap_log_error(...). */
+#define SHMCB_MASK_DBG(pHeader, id) \
+                *(id), (*(id) & ((pHeader)->subcache_num - 1))
+
+/* This macro takes a pointer to a subcache and a zero-based index and returns
+ * a pointer to the corresponding SHMCBIndex. */
+#define SHMCB_INDEX(pSubcache, num) \
+                ((SHMCBIndex *)(((unsigned char *)pSubcache) + \
+                                sizeof(SHMCBSubcache)) + num)
+
+/* This macro takes a pointer to the header and a subcache and returns a
+ * pointer to the corresponding data area. */
+#define SHMCB_DATA(pHeader, pSubcache) \
+                ((unsigned char *)(pSubcache) + (pHeader)->subcache_data_offset)
 
 /*
- * Forward function prototypes.
+ * Cyclic functions - assists in "wrap-around"/modulo logic
  */
 
-/* Functions for working around data-alignment-picky systems (sparcs,
-   Irix, etc). These use "memcpy" as a way of foxing these systems into
-   treating the composite types as byte-arrays rather than higher-level
-   primitives that it prefers to have 4-(or 8-)byte aligned. I don't
-   envisage this being a performance issue as a couple of 2 or 4 byte
-   memcpys can hardly make a dent on the massive memmove operations this
-   cache technique avoids, nor the overheads of ASN en/decoding. */
-static unsigned int shmcb_get_safe_uint(unsigned int *);
-static void shmcb_set_safe_uint_ex(unsigned char *, const unsigned char *);
-#define shmcb_set_safe_uint(pdest, src) \
-        do { \
-                unsigned int tmp_uint = src; \
-                shmcb_set_safe_uint_ex((unsigned char *)pdest, \
-                        (const unsigned char *)(&tmp_uint)); \
-        } while(0)
-#if 0 /* Unused so far */
-static unsigned long shmcb_get_safe_ulong(unsigned long *);
-static void shmcb_set_safe_ulong_ex(unsigned char *, const unsigned char *);
-#define shmcb_set_safe_ulong(pdest, src) \
-        do { \
-                unsigned long tmp_ulong = src; \
-                shmcb_set_safe_ulong_ex((unsigned char *)pdest, \
-                        (const unsigned char *)(&tmp_ulong)); \
-        } while(0)
-#endif
-static time_t shmcb_get_safe_time(time_t *);
-static void shmcb_set_safe_time_ex(unsigned char *, const unsigned char *);
-#define shmcb_set_safe_time(pdest, src) \
-        do { \
-                time_t tmp_time = src; \
-                shmcb_set_safe_time_ex((unsigned char *)pdest, \
-                        (const unsigned char *)(&tmp_time)); \
-        } while(0)
+/* Addition modulo 'mod' */
+#define SHMCB_CYCLIC_INCREMENT(val,inc,mod) \
+                (((val) + (inc)) % (mod))
 
-/* This is used to persuade the compiler from using an inline memset()
- * which has no respect for alignment, since the size parameter is
- * often a compile-time constant.  GCC >= 4 will aggressively inline
- * static functions, so it's marked as explicitly not-inline. */
-#if defined(__GNUC__) && __GNUC__ > 3
-__attribute__((__noinline__))
-#endif
-static void shmcb_safe_clear(void *ptr, size_t size)
-{
-        memset(ptr, 0, size);
-}
-
-/* Underlying functions for session-caching */
-static BOOL shmcb_init_memory(server_rec *, void *, unsigned int);
-static BOOL shmcb_store_session(server_rec *, void *, UCHAR *, int, SSL_SESSION *, time_t);
-static SSL_SESSION *shmcb_retrieve_session(server_rec *, void *, UCHAR *, int);
-static BOOL shmcb_remove_session(server_rec *, void *, UCHAR *, int);
+/* Subtraction (or "distance between") modulo 'mod' */
+#define SHMCB_CYCLIC_SPACE(val1,val2,mod) \
+                ((val2) >= (val1) ? ((val2) - (val1)) : \
+                        ((val2) + (mod) - (val1)))
 
-/* Utility functions for manipulating the structures */
-static void shmcb_get_header(void *, SHMCBHeader **);
-static BOOL shmcb_get_division(SHMCBHeader *, SHMCBQueue *, SHMCBCache *, unsigned int);
-static SHMCBIndex *shmcb_get_index(const SHMCBQueue *, unsigned int);
-static unsigned int shmcb_expire_division(server_rec *, SHMCBQueue *, SHMCBCache *);
-static BOOL shmcb_insert_encoded_session(server_rec *, SHMCBQueue *, SHMCBCache *, unsigned char *, unsigned int, unsigned char *, time_t);
-static SSL_SESSION *shmcb_lookup_session_id(server_rec *, SHMCBQueue *, SHMCBCache *, UCHAR *, unsigned int);
-static BOOL shmcb_remove_session_id(server_rec *, SHMCBQueue *, SHMCBCache *, UCHAR *, unsigned int);
-
-/*
- * Data-alignment functions (a.k.a. avoidance tactics)
- *
- * NB: On HPUX (and possibly others) there is a *very* mischievous little
- * "optimisation" in the compilers where it will convert the following;
- *      memcpy(dest_ptr, &source, sizeof(unsigned int));
- * (where dest_ptr is of type (unsigned int *) and source is (unsigned int))
- * into;
- *      *dest_ptr = source; (or *dest_ptr = *(&source), not sure).
- * Either way, it completely destroys the whole point of these _safe_
- * functions, because the assignment operation will fall victim to the
- * architecture's byte-alignment dictations, whereas the memcpy (as a
- * byte-by-byte copy) should not. sigh. So, if you're wondering about the
- * apparently unnecessary conversions to (unsigned char *) in these
- * functions, you now have an explanation. Don't just revert them back and
- * say "ooh look, it still works" - if you try it on HPUX (well, 32-bit
- * HPUX 11.00 at least) you may find it fails with a SIGBUS. :-(
- */
-
-static unsigned int shmcb_get_safe_uint(unsigned int *ptr)
-{
-    unsigned int ret;
-    shmcb_set_safe_uint_ex((unsigned char *)(&ret),
-                    (const unsigned char *)ptr);
-    return ret;
-}
-
-static void shmcb_set_safe_uint_ex(unsigned char *dest,
-                                const unsigned char *src)
+/* A "normal-to-cyclic" memcpy. */
+static void shmcb_cyclic_ntoc_memcpy(unsigned int buf_size, unsigned char *data,
+                                     unsigned int dest_offset, unsigned char *src,
+                                     unsigned int src_len)
 {
-    memcpy(dest, src, sizeof(unsigned int));
-}
-
-#if 0 /* Unused so far */
-static unsigned long shmcb_get_safe_ulong(unsigned long *ptr)
-{
-    unsigned long ret;
-    shmcb_set_safe_ulong_ex((unsigned char *)(&ret),
-                    (const unsigned char *)ptr);
-    return ret;
+    if (dest_offset + src_len < buf_size)
+        /* It be copied all in one go */
+        memcpy(data + dest_offset, src, src_len);
+    else {
+        /* Copy the two splits */
+        memcpy(data + dest_offset, src, buf_size - dest_offset);
+        memcpy(data, src + buf_size - dest_offset,
+               src_len + dest_offset - buf_size);
+    }
 }
 
-static void shmcb_set_safe_ulong_ex(unsigned char *dest,
-                                const unsigned char *src)
+/* A "cyclic-to-normal" memcpy. */
+static void shmcb_cyclic_cton_memcpy(unsigned int buf_size, unsigned char *dest,
+                                     unsigned char *data, unsigned int src_offset,
+                                     unsigned int src_len)
 {
-    memcpy(dest, src, sizeof(unsigned long));
+    if (src_offset + src_len < buf_size)
+        /* It be copied all in one go */
+        memcpy(dest, data + src_offset, src_len);
+    else {
+        /* Copy the two splits */
+        memcpy(dest, data + src_offset, buf_size - src_offset);
+        memcpy(dest + buf_size - src_offset, data,
+               src_len + src_offset - buf_size);
+    }
 }
-#endif
 
-static time_t shmcb_get_safe_time(time_t * ptr)
-{
-    time_t ret;
-    shmcb_set_safe_time_ex((unsigned char *)(&ret),
-                    (const unsigned char *)ptr);
-    return ret;
-}
+/* Prototypes for low-level subcache operations */
+static void shmcb_subcache_expire(server_rec *, SHMCBHeader *, SHMCBSubcache *);
+static BOOL shmcb_subcache_store(server_rec *, SHMCBHeader *, SHMCBSubcache *,
+                                 UCHAR *, unsigned int, UCHAR *, time_t);
+static SSL_SESSION *shmcb_subcache_retrieve(server_rec *, SHMCBHeader *, SHMCBSubcache *,
+                                            UCHAR *, unsigned int);
+static BOOL shmcb_subcache_remove(server_rec *, SHMCBHeader *, SHMCBSubcache *,
+                                 UCHAR *, unsigned int);
 
-static void shmcb_set_safe_time_ex(unsigned char *dest,
-                                const unsigned char *src)
-{
-    memcpy(dest, src, sizeof(time_t));
-}
 /*
-**
-** High-Level "handlers" as per ssl_scache.c
-**
-*/
+ * High-Level "handlers" as per ssl_scache.c
+ * subcache internals are deferred to shmcb_subcache_*** functions lower down
+ */
 
 void ssl_scache_shmcb_init(server_rec *s, apr_pool_t *p)
 {
@@ -335,10 +179,10 @@ void ssl_scache_shmcb_init(server_rec *s, apr_pool_t *p)
     void *shm_segment;
     apr_size_t shm_segsize;
     apr_status_t rv;
+    SHMCBHeader *header;
+    unsigned int num_subcache, num_idx, loop;
 
-    /*
-     * Create shared memory segment
-     */
+    /* Create shared memory segment */
     if (mc->szSessionCacheDataFile == NULL) {
         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
                      "SSLSessionCache required");
@@ -346,10 +190,9 @@ void ssl_scache_shmcb_init(server_rec *s, apr_pool_t *p)
     }
 
     /* Use anonymous shm by default, fall back on name-based. */
-    rv = apr_shm_create(&(mc->pSessionCacheDataMM),
-                        mc->nSessionCacheDataSize,
+    rv = apr_shm_create(&(mc->pSessionCacheDataMM), 
+                        mc->nSessionCacheDataSize, 
                         NULL, mc->pPool);
-
     if (APR_STATUS_IS_ENOTIMPL(rv)) {
         /* For a name-based segment, remove it first in case of a
          * previous unclean shutdown. */
@@ -362,32 +205,95 @@ void ssl_scache_shmcb_init(server_rec *s, apr_pool_t *p)
     }
 
     if (rv != APR_SUCCESS) {
-        char buf[100];
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "Cannot allocate shared memory: (%d)%s", rv,
-                     apr_strerror(rv, buf, sizeof(buf)));
+        ap_log_error(APLOG_MARK, APLOG_ERR, rv, s,
+                     "could not allocate shared memory for shmcb "
+                     "session cache");
         ssl_die();
     }
+
     shm_segment = apr_shm_baseaddr_get(mc->pSessionCacheDataMM);
     shm_segsize = apr_shm_size_get(mc->pSessionCacheDataMM);
-
+    if (shm_segsize < (5 * sizeof(SHMCBHeader))) {
+        /* the segment is ridiculously small, bail out */
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                     "shared memory segment too small");
+        ssl_die();
+    }
     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
                  "shmcb_init allocated %" APR_SIZE_T_FMT
                  " bytes of shared memory",
                  shm_segsize);
-    if (!shmcb_init_memory(s, shm_segment, shm_segsize)) {
+    /* Discount the header */
+    shm_segsize -= sizeof(SHMCBHeader);
+    /* Select the number of subcaches to create and how many indexes each
+     * should contain based on the size of the memory (the header has already
+     * been subtracted). Typical non-client-auth sslv3/tlsv1 sessions are
+     * around 150 bytes, so erring to division by 120 helps ensure we would
+     * exhaust data storage before index storage (except sslv2, where it's
+     * *slightly* the other way). From there, we select the number of subcaches
+     * to be a power of two, such that the number of indexes per subcache at
+     * least twice the number of subcaches. */
+    num_idx = (shm_segsize) / 120;
+    num_subcache = 256;
+    while ((num_idx / num_subcache) < (2 * num_subcache))
+        num_subcache /= 2;
+    num_idx /= num_subcache;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "for %" APR_SIZE_T_FMT " bytes (%" APR_SIZE_T_FMT 
+                 " including header), recommending %u subcaches, "
+                 "%u indexes each", shm_segsize,
+                 shm_segsize + sizeof(SHMCBHeader), num_subcache, num_idx);
+    if (num_idx < 5) {
+        /* we're still too small, bail out */
         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "Failure initialising 'shmcb' shared memory");
+                     "shared memory segment too small");
         ssl_die();
     }
+    /* OK, we're sorted */
+    header = shm_segment;
+    header->stat_stores = 0;
+    header->stat_expiries = 0;
+    header->stat_scrolled = 0;
+    header->stat_retrieves_hit = 0;
+    header->stat_retrieves_miss = 0;
+    header->stat_removes_hit = 0;
+    header->stat_removes_miss = 0;
+    header->subcache_num = num_subcache;
+    /* Convert the subcache size (in bytes) to a value that is suitable for
+     * structure alignment on the host platform, by rounding down if necessary.
+     * This assumes that sizeof(unsigned long) provides an appropriate
+     * alignment unit.  */
+    header->subcache_size = ((size_t)(shm_segsize / num_subcache) &
+                             ~(size_t)(sizeof(unsigned long) - 1));
+    header->subcache_data_offset = sizeof(SHMCBSubcache) +
+                                   num_idx * sizeof(SHMCBIndex);
+    header->subcache_data_size = header->subcache_size -
+                                 header->subcache_data_offset;
+    header->index_num = num_idx;
+
+    /* Output trace info */
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "shmcb_init_memory choices follow");
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "subcache_num = %u", header->subcache_num);
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "subcache_size = %u", header->subcache_size);
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "subcache_data_offset = %u", header->subcache_data_offset);
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "subcache_data_size = %u", header->subcache_data_size);
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "index_num = %u", header->index_num);
+    /* The header is done, make the caches empty */
+    for (loop = 0; loop < header->subcache_num; loop++) {
+        SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop);
+        subcache->idx_pos = subcache->idx_used = 0;
+        subcache->data_pos = subcache->data_used = 0;
+    }
     ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
                  "Shared memory session cache initialised");
-
-    /*
-     * Success ...
-     */
+    /* Success ... */
     mc->tSessionCacheDataTable = shm_segment;
-    return;
 }
 
 void ssl_scache_shmcb_kill(server_rec *s)
@@ -406,19 +312,41 @@ BOOL ssl_scache_shmcb_store(server_rec *s, UCHAR *id, int idlen,
 {
     SSLModConfigRec *mc = myModConfig(s);
     BOOL to_return = FALSE;
+    unsigned char encoded[SSL_SESSION_MAX_DER];
+    unsigned char *ptr_encoded;
+    unsigned int len_encoded;
+    SHMCBHeader *header = mc->tSessionCacheDataTable;
+    SHMCBSubcache *subcache = SHMCB_MASK(header, id);
 
     ssl_mutex_on(s);
-    if (!shmcb_store_session(s, mc->tSessionCacheDataTable, id, idlen,
-                             pSession, timeout))
-        /* in this cache engine, "stores" should never fail. */
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "ssl_scache_shmcb_store (0x%02x -> subcache %d)",
+                 SHMCB_MASK_DBG(header, id));
+    if (idlen < 4) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
+                "(%u bytes)", idlen);
+        goto done;
+    }
+    /* Serialise the session. */
+    len_encoded = i2d_SSL_SESSION(pSession, NULL);
+    if (len_encoded > SSL_SESSION_MAX_DER) {
         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "'shmcb' code was unable to store a "
-                     "session in the cache.");
-    else {
-        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                     "shmcb_store successful");
-        to_return = TRUE;
+                     "session is too big (%u bytes)", len_encoded);
+        goto done;
     }
+    ptr_encoded = encoded;
+    len_encoded = i2d_SSL_SESSION(pSession, &ptr_encoded);
+    if (!shmcb_subcache_store(s, header, subcache, encoded,
+                              len_encoded, id, timeout)) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
+                     "can't store a session!");
+        goto done;
+    }
+    header->stat_stores++;
+    to_return = TRUE;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "leaving ssl_scache_shmcb_store successfully");
+done:
     ssl_mutex_off(s);
     return to_return;
 }
@@ -426,698 +354,235 @@ BOOL ssl_scache_shmcb_store(server_rec *s, UCHAR *id, int idlen,
 SSL_SESSION *ssl_scache_shmcb_retrieve(server_rec *s, UCHAR *id, int idlen)
 {
     SSLModConfigRec *mc = myModConfig(s);
-    SSL_SESSION *pSession;
+    SSL_SESSION *pSession = NULL;
+    SHMCBHeader *header = mc->tSessionCacheDataTable;
+    SHMCBSubcache *subcache = SHMCB_MASK(header, id);
 
     ssl_mutex_on(s);
-    pSession = shmcb_retrieve_session(s, mc->tSessionCacheDataTable, id, idlen);
-    ssl_mutex_off(s);
-    if (pSession)
-        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                     "shmcb_retrieve had a hit");
-    else {
-        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                     "shmcb_retrieve had a miss");
-        ap_log_error(APLOG_MARK, APLOG_INFO, 0, s,
-                     "Client requested a 'session-resume' but "
-                     "we have no such session.");
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "ssl_scache_shmcb_retrieve (0x%02x -> subcache %d)",
+                 SHMCB_MASK_DBG(header, id));
+    if (idlen < 4) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
+                "(%u bytes)", idlen);
+        goto done;
     }
+    /* Get the session corresponding to the session_id or NULL if it doesn't
+     * exist (or is flagged as "removed"). */
+    pSession = shmcb_subcache_retrieve(s, header, subcache, id, idlen);
+    if (pSession)
+        header->stat_retrieves_hit++;
+    else
+        header->stat_retrieves_miss++;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "leaving ssl_scache_shmcb_retrieve successfully");
+done:
+    ssl_mutex_off(s);
     return pSession;
 }
 
 void ssl_scache_shmcb_remove(server_rec *s, UCHAR *id, int idlen)
 {
     SSLModConfigRec *mc = myModConfig(s);
+    SHMCBHeader *header = mc->tSessionCacheDataTable;
+    SHMCBSubcache *subcache = SHMCB_MASK(header, id);
 
     ssl_mutex_on(s);
-    shmcb_remove_session(s, mc->tSessionCacheDataTable, id, idlen);
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "ssl_scache_shmcb_remove (0x%02x -> subcache %d)",
+                 SHMCB_MASK_DBG(header, id));
+    if (idlen < 4) {
+        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
+                "(%u bytes)", idlen);
+        goto done;
+    }
+    if (shmcb_subcache_remove(s, header, subcache, id, idlen))
+        header->stat_removes_hit++;
+    else
+        header->stat_removes_miss++;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "leaving ssl_scache_shmcb_remove successfully");
+done:
     ssl_mutex_off(s);
 }
 
 void ssl_scache_shmcb_status(request_rec *r, int flags, apr_pool_t *p)
 {
-    SSLModConfigRec *mc = myModConfig(r->server);
-    SHMCBHeader *header;
-    SHMCBQueue queue;
-    SHMCBCache cache;
-    SHMCBIndex *idx;
-    unsigned int loop, total, cache_total, non_empty_divisions;
+    server_rec *s = r->server;
+    SSLModConfigRec *mc = myModConfig(s);
+    void *shm_segment = apr_shm_baseaddr_get(mc->pSessionCacheDataMM);
+    SHMCBHeader *header = shm_segment;
+    unsigned int loop, total = 0, cache_total = 0, non_empty_subcaches = 0;
+    time_t idx_expiry, min_expiry = 0, max_expiry = 0, average_expiry = 0;
+    time_t now = time(NULL);
+    double expiry_total = 0;
     int index_pct, cache_pct;
-    double expiry_total;
-    time_t average_expiry, now, max_expiry, min_expiry, idxexpiry;
 
     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "inside shmcb_status");
-
-    /* Get the header structure. */
-    shmcb_get_header(mc->tSessionCacheDataTable, &header);
-    total = cache_total = non_empty_divisions = 0;
-    average_expiry = max_expiry = min_expiry = 0;
-    expiry_total = 0;
-
-    /* It may seem strange to grab "now" at this point, but in theory
-     * we should never have a negative threshold but grabbing "now" after
-     * the loop (which performs expiries) could allow that chance. */
-    now = time(NULL);
-    for (loop = 0; loop <= header->division_mask; loop++) {
-        if (shmcb_get_division(header, &queue, &cache, loop)) {
-            shmcb_expire_division(r->server, &queue, &cache);
-            total += shmcb_get_safe_uint(queue.pos_count);
-            cache_total += shmcb_get_safe_uint(cache.pos_count);
-            if (shmcb_get_safe_uint(queue.pos_count) > 0) {
-                idx = shmcb_get_index(&queue,
-                                     shmcb_get_safe_uint(queue.first_pos));
-                non_empty_divisions++;
-                idxexpiry = shmcb_get_safe_time(&(idx->expires));
-                expiry_total += (double) idxexpiry;
-                max_expiry = (idxexpiry > max_expiry ? idxexpiry :
-                              max_expiry);
-                if (min_expiry == 0)
-                    min_expiry = idxexpiry;
-                else
-                    min_expiry = (idxexpiry < min_expiry ? idxexpiry :
-                                  min_expiry);
-            }
+    /* Perform the iteration inside the mutex to avoid corruption or invalid
+     * pointer arithmetic. The rest of our logic uses read-only header data so
+     * doesn't need the lock. */
+    ssl_mutex_on(s);
+    /* Iterate over the subcaches */
+    for (loop = 0; loop < header->subcache_num; loop++) {
+        SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop);
+        shmcb_subcache_expire(s, header, subcache);
+        total += subcache->idx_used;
+        cache_total += subcache->data_used;
+        if (subcache->idx_used) {
+            SHMCBIndex *idx = SHMCB_INDEX(subcache, subcache->idx_pos);
+            non_empty_subcaches++;
+            idx_expiry = idx->expires;
+            expiry_total += (double)idx_expiry;
+            max_expiry = ((idx_expiry > max_expiry) ? idx_expiry : max_expiry);
+            if (!min_expiry)
+                min_expiry = idx_expiry;
+            else
+                min_expiry = ((idx_expiry < min_expiry) ? idx_expiry : min_expiry);
         }
     }
-    index_pct = (100 * total) / (header->index_num * (header->division_mask + 1));
-    cache_pct = (100 * cache_total) / (header->cache_data_size * (header->division_mask + 1));
+    ssl_mutex_off(s);
+    index_pct = (100 * total) / (header->index_num *
+                                 header->subcache_num);
+    cache_pct = (100 * cache_total) / (header->subcache_data_size *
+                                       header->subcache_num);
+    /* Generate HTML */
     ap_rprintf(r, "cache type: <b>SHMCB</b>, shared memory: <b>%d</b> "
                "bytes, current sessions: <b>%d</b><br>",
                mc->nSessionCacheDataSize, total);
-    ap_rprintf(r, "sub-caches: <b>%d</b>, indexes per sub-cache: "
-               "<b>%d</b><br>", (int) header->division_mask + 1,
-               (int) header->index_num);
-    if (non_empty_divisions != 0) {
-        average_expiry = (time_t)(expiry_total / (double)non_empty_divisions);
+    ap_rprintf(r, "subcaches: <b>%d</b>, indexes per subcache: <b>%d</b><br>",
+               header->subcache_num, header->index_num);
+    if (non_empty_subcaches) {
+        average_expiry = (time_t)(expiry_total / (double)non_empty_subcaches);
         ap_rprintf(r, "time left on oldest entries' SSL sessions: ");
         if (now < average_expiry)
             ap_rprintf(r, "avg: <b>%d</b> seconds, (range: %d...%d)<br>",
-                       (int)(average_expiry - now), (int) (min_expiry - now),
+                       (int)(average_expiry - now),
+                       (int)(min_expiry - now),
                        (int)(max_expiry - now));
         else
-            ap_rprintf(r, "expiry threshold: <b>Calculation Error!</b>"
-                       "<br>");
-
+            ap_rprintf(r, "expiry_threshold: <b>Calculation error!</b><br>");
     }
-    ap_rprintf(r, "index usage: <b>%d%%</b>, cache usage: <b>%d%%</b>"
-               "<br>", index_pct, cache_pct);
+
+    ap_rprintf(r, "index usage: <b>%d%%</b>, cache usage: <b>%d%%</b><br>",
+               index_pct, cache_pct);
     ap_rprintf(r, "total sessions stored since starting: <b>%lu</b><br>",
-               header->num_stores);
+               header->stat_stores);
     ap_rprintf(r, "total sessions expired since starting: <b>%lu</b><br>",
-               header->num_expiries);
-    ap_rprintf(r, "total (pre-expiry) sessions scrolled out of the "
-               "cache: <b>%lu</b><br>", header->num_scrolled);
+               header->stat_expiries);
+    ap_rprintf(r, "total (pre-expiry) sessions scrolled out of the cache: "
+               "<b>%lu</b><br>", header->stat_scrolled);
     ap_rprintf(r, "total retrieves since starting: <b>%lu</b> hit, "
-               "<b>%lu</b> miss<br>", header->num_retrieves_hit,
-               header->num_retrieves_miss);
+               "<b>%lu</b> miss<br>", header->stat_retrieves_hit,
+               header->stat_retrieves_miss);
     ap_rprintf(r, "total removes since starting: <b>%lu</b> hit, "
-               "<b>%lu</b> miss<br>", header->num_removes_hit,
-               header->num_removes_miss);
+               "<b>%lu</b> miss<br>", header->stat_removes_hit,
+               header->stat_removes_miss);
     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, "leaving shmcb_status");
-    return;
 }
 
 /*
-**
-** Memory manipulation and low-level cache operations
-**
-*/
-
-static BOOL shmcb_init_memory(
-    server_rec *s, void *shm_mem,
-    unsigned int shm_mem_size)
-{
-    SHMCBHeader *header;
-    SHMCBQueue queue;
-    SHMCBCache cache;
-    unsigned int temp, loop, granularity;
-
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "entered shmcb_init_memory()");
-
-    /* Calculate some sizes... */
-    temp = sizeof(SHMCBHeader);
-
-    /* If the segment is ridiculously too small, bail out */
-    if (shm_mem_size < (2*temp)) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "shared memory segment too small");
-        return FALSE;
-    }
-
-    /* Make temp the amount of memory without the header */
-    temp = shm_mem_size - temp;
-
-    /* Work on the basis that you need 10 bytes index for each session
-     * (approx 150 bytes), which is to divide temp by 160 - and then
-     * make sure we err on having too index space to burn even when
-     * the cache is full, which is a lot less stupid than having
-     * having not enough index space to utilise the whole cache!. */
-    temp /= 120;
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "for %u bytes, recommending %u indexes",
-                 shm_mem_size, temp);
-
-    /* We should divide these indexes evenly amongst the queues. Try
-     * to get it so that there are roughly half the number of divisions
-     * as there are indexes in each division. */
-    granularity = 256;
-    while ((temp / granularity) < (2 * granularity))
-        granularity /= 2;
-
-    /* So we have 'granularity' divisions, set 'temp' equal to the
-     * number of indexes in each division. */
-    temp /= granularity;
-
-    /* Too small? Bail ... */
-    if (temp < 5) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "shared memory segment too small");
-        return FALSE;
-    }
-
-    /* OK, we're sorted - from here on in, the return should be TRUE */
-    header = (SHMCBHeader *)shm_mem;
-    header->division_mask = (unsigned char)(granularity - 1);
-    header->division_offset = sizeof(SHMCBHeader);
-    header->index_num = temp;
-    header->index_offset = (2 * sizeof(unsigned int));
-    header->index_size = sizeof(SHMCBIndex);
-    header->queue_size = header->index_offset +
-                         (header->index_num * header->index_size);
-
-    /* Now calculate the space for each division */
-    temp = shm_mem_size - header->division_offset;
-    header->division_size = temp / granularity;
-
-    /* Calculate the space left in each division for the cache */
-    temp -= header->queue_size;
-    header->cache_data_offset = (2 * sizeof(unsigned int));
-    header->cache_data_size = header->division_size -
-                              header->queue_size - header->cache_data_offset;
-
-    /* Output trace info */
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "shmcb_init_memory choices follow");
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "division_mask = 0x%02X", header->division_mask);
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "division_offset = %u", header->division_offset);
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                  "division_size = %u", header->division_size);
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                  "queue_size = %u", header->queue_size);
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                  "index_num = %u", header->index_num);
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                  "index_offset = %u", header->index_offset);
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                  "index_size = %u", header->index_size);
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                  "cache_data_offset = %u", header->cache_data_offset);
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                  "cache_data_size = %u", header->cache_data_size);
-
-    /* The header is done, make the caches empty */
-    for (loop = 0; loop < granularity; loop++) {
-        if (!shmcb_get_division(header, &queue, &cache, loop))
-            ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "shmcb_init_memory, " "internal error");
-        shmcb_set_safe_uint(cache.first_pos, 0);
-        shmcb_set_safe_uint(cache.pos_count, 0);
-        shmcb_set_safe_uint(queue.first_pos, 0);
-        shmcb_set_safe_uint(queue.pos_count, 0);
-    }
-
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "leaving shmcb_init_memory()");
-    return TRUE;
-}
+ * Subcache-level cache operations 
+ */
 
-static BOOL shmcb_store_session(
-    server_rec *s, void *shm_segment, UCHAR *id,
-    int idlen, SSL_SESSION * pSession,
-    time_t timeout)
+static void shmcb_subcache_expire(server_rec *s, SHMCBHeader *header,
+                                  SHMCBSubcache *subcache)
 {
-    SHMCBHeader *header;
-    SHMCBQueue queue;
-    SHMCBCache cache;
-    unsigned char masked_index;
-    unsigned char encoded[SSL_SESSION_MAX_DER];
-    unsigned char *ptr_encoded;
-    unsigned int len_encoded;
-    time_t expiry_time;
-    unsigned char *session_id = SSL_SESSION_get_session_id(pSession);
-
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "inside shmcb_store_session");
+    time_t now = time(NULL);
+    unsigned int loop = 0;
+    unsigned int new_idx_pos = subcache->idx_pos;
+    SHMCBIndex *idx = NULL;
 
-    /* Get the header structure, which division this session will fall into etc. */
-    shmcb_get_header(shm_segment, &header);
-    masked_index = session_id[0] & header->division_mask;
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "session_id[0]=%u, masked index=%u",
-                 session_id[0], masked_index);
-    if (!shmcb_get_division(header, &queue, &cache, (unsigned int)masked_index)) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "shmcb_store_session internal error");
-        return FALSE;
+    while (loop < subcache->idx_used) {
+        idx = SHMCB_INDEX(subcache, new_idx_pos);
+        if (idx->expires > now)
+            /* it hasn't expired yet, we're done iterating */
+            break;
+        loop++;
+        new_idx_pos = SHMCB_CYCLIC_INCREMENT(new_idx_pos, 1, header->index_num);
     }
-
-    /* Serialise the session, work out how much we're dealing
-     * with. NB: This check could be removed if we're not paranoid
-     * or we find some assurance that it will never be necessary. */
-    len_encoded = i2d_SSL_SESSION(pSession, NULL);
-    if (len_encoded > SSL_SESSION_MAX_DER) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "session is too big (%u bytes)", len_encoded);
-        return FALSE;
-    }
-    ptr_encoded = encoded;
-    len_encoded = i2d_SSL_SESSION(pSession, &ptr_encoded);
-    expiry_time = timeout;
-    if (!shmcb_insert_encoded_session(s, &queue, &cache, encoded,
-                                     len_encoded, session_id,
-                                     expiry_time)) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "can't store a session!");
-        return FALSE;
+    if (!loop)
+        /* Nothing to do */
+        return;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "will be expiring %u sessions", loop);
+    if (loop == subcache->idx_used) {
+        /* We're expiring everything, piece of cake */
+        subcache->idx_used = 0;
+        subcache->data_used = 0;
+    } else {
+        /* There remain other indexes, so we can use idx to adjust 'data' */
+        unsigned int diff = SHMCB_CYCLIC_SPACE(subcache->data_pos,
+                                               idx->data_pos,
+                                               header->subcache_data_size);
+        /* Adjust the indexes */
+        subcache->idx_used -= loop;
+        subcache->idx_pos = new_idx_pos;
+        /* Adjust the data area */
+        subcache->data_used -= diff;
+        subcache->data_pos = idx->data_pos;
     }
+    header->stat_expiries += loop;
     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "leaving shmcb_store successfully");
-    header->num_stores++;
-    return TRUE;
+                 "we now have %u sessions", subcache->idx_used);
 }
 
-static SSL_SESSION *shmcb_retrieve_session(
-    server_rec *s, void *shm_segment,
-    UCHAR *id, int idlen)
+static BOOL shmcb_subcache_store(server_rec *s, SHMCBHeader *header,
+                                 SHMCBSubcache *subcache, 
+                                 UCHAR *data, unsigned int data_len,
+                                 UCHAR *id, time_t expiry)
 {
-    SHMCBHeader *header;
-    SHMCBQueue queue;
-    SHMCBCache cache;
-    unsigned char masked_index;
-    SSL_SESSION *pSession;
-
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "inside shmcb_retrieve_session");
-    if (idlen < 2) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "unusably short session_id provided "
-                "(%u bytes)", idlen);
-        return FALSE;
-    }
+    unsigned int new_offset, new_idx;
+    SHMCBIndex *idx;
 
-    /* Get the header structure, which division this session lookup
-     * will come from etc. */
-    shmcb_get_header(shm_segment, &header);
-    masked_index = id[0] & header->division_mask;
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "id[0]=%u, masked index=%u", id[0], masked_index);
-    if (!shmcb_get_division(header, &queue, &cache, (unsigned int) masked_index)) {
+    /* Sanity check the input */
+    if ((data_len > header->subcache_data_size) || (data_len > SSL_SESSION_MAX_DER)) {
         ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "shmcb_retrieve_session internal error");
-        header->num_retrieves_miss++;
-        return FALSE;
-    }
-
-    /* Get the session corresponding to the session_id or NULL if it
-     * doesn't exist (or is flagged as "removed"). */
-    pSession = shmcb_lookup_session_id(s, &queue, &cache, id, idlen);
-    if (pSession)
-        header->num_retrieves_hit++;
-    else
-        header->num_retrieves_miss++;
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "leaving shmcb_retrieve_session");
-    return pSession;
-}
-
-static BOOL shmcb_remove_session(
-    server_rec *s, void *shm_segment,
-    UCHAR *id, int idlen)
-{
-    SHMCBHeader *header;
-    SHMCBQueue queue;
-    SHMCBCache cache;
-    unsigned char masked_index;
-    BOOL res;
-
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "inside shmcb_remove_session");
-    if (id == NULL) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "remove called with NULL session_id!");
-        return FALSE;
-    }
-
-    /* Get the header structure, which division this session remove
-     * will happen in etc. */
-    shmcb_get_header(shm_segment, &header);
-    masked_index = id[0] & header->division_mask;
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "id[0]=%u, masked index=%u", id[0], masked_index);
-    if (!shmcb_get_division(header, &queue, &cache, (unsigned int)masked_index)) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, "shmcb_remove_session, internal error");
-        header->num_removes_miss++;
+                     "inserting session larger (%d) than subcache data area (%d)",
+                     data_len, header->subcache_data_size);
         return FALSE;
     }
-    res = shmcb_remove_session_id(s, &queue, &cache, id, idlen);
-    if (res)
-        header->num_removes_hit++;
-    else
-        header->num_removes_miss++;
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "leaving shmcb_remove_session");
-    return res;
-}
-
-
-/*
-**
-** Weirdo cyclic buffer functions
-**
-*/
-
-/* This gets used in the cyclic "index array" (in the 'Queue's) and
- * in the cyclic 'Cache's too ... you provide the "width" of the
- * cyclic store, the starting position and how far to move (with
- * wrapping if necessary). Basically it's addition modulo buf_size. */
-static unsigned int shmcb_cyclic_increment(
-    unsigned int buf_size,
-    unsigned int start_pos,
-    unsigned int to_add)
-{
-    start_pos += to_add;
-    while (start_pos >= buf_size)
-        start_pos -= buf_size;
-    return start_pos;
-}
-
-/* Given two positions in a cyclic buffer, calculate the "distance".
- * This is to cover the case ("non-trivial") where the 'next' offset
- * is to the left of the 'start' offset. NB: This calculates the
- * space inclusive of one end-point but not the other. There is an
- * ambiguous case (which is why we use the <start_pos,offset>
- * coordinate system rather than <start_pos,end_pos> one) when 'start'
- * is the same as 'next'. It could indicate the buffer is full or it
- * can indicate the buffer is empty ... I choose the latter as it's
- * easier and usually necessary to check if the buffer is full anyway
- * before doing incremental logic (which is this useful for), but we
- * definitely need the empty case handled - in fact it's our starting
- * state!! */
-static unsigned int shmcb_cyclic_space(
-    unsigned int buf_size,
-    unsigned int start_offset,
-    unsigned int next_offset)
-{
-    /* Is it the trivial case? */
-    if (start_offset <= next_offset)
-        return (next_offset - start_offset);              /* yes */
-    else
-        return ((buf_size - start_offset) + next_offset); /* no */
-}
-
-/* A "normal-to-cyclic" memcpy ... this takes a linear block of
- * memory and copies it onto a cyclic buffer. The purpose and
- * function of this is pretty obvious, you need to cover the case
- * that the destination (cyclic) buffer has to wrap round. */
-static void shmcb_cyclic_ntoc_memcpy(
-    unsigned int buf_size,
-    unsigned char *data,
-    unsigned int dest_offset,
-    unsigned char *src, unsigned int src_len)
-{
-    /* Cover the case that src_len > buf_size */
-    if (src_len > buf_size)
-        src_len = buf_size;
-
-    /* Can it be copied all in one go? */
-    if (dest_offset + src_len < buf_size)
-        /* yes */
-        memcpy(data + dest_offset, src, src_len);
-    else {
-        /* no */
-        memcpy(data + dest_offset, src, buf_size - dest_offset);
-        memcpy(data, src + buf_size - dest_offset,
-               src_len + dest_offset - buf_size);
-    }
-    return;
-}
-
-/* A "cyclic-to-normal" memcpy ... given the last function, this
- * one's purpose is clear, it copies out of a cyclic buffer handling
- * wrapping. */
-static void shmcb_cyclic_cton_memcpy(
-    unsigned int buf_size,
-    unsigned char *dest,
-    unsigned char *data,
-    unsigned int src_offset,
-    unsigned int src_len)
-{
-    /* Cover the case that src_len > buf_size */
-    if (src_len > buf_size)
-        src_len = buf_size;
-
-    /* Can it be copied all in one go? */
-    if (src_offset + src_len < buf_size)
-        /* yes */
-        memcpy(dest, data + src_offset, src_len);
-    else {
-        /* no */
-        memcpy(dest, data + src_offset, buf_size - src_offset);
-        memcpy(dest + buf_size - src_offset, data,
-               src_len + src_offset - buf_size);
-    }
-    return;
-}
-
-/* Here's the cool hack that makes it all work ... by simply
- * making the first collection of bytes *be* our header structure
- * (casting it into the C structure), we have the perfect way to
- * maintain state in a shared-memory session cache from one call
- * (and process) to the next, use the shared memory itself! The
- * original mod_ssl shared-memory session cache uses variables
- * inside the context, but we simply use that for storing the
- * pointer to the shared memory itself. And don't forget, after
- * Apache's initialisation, this "header" is constant/read-only
- * so we can read it outside any locking.
- * <grin> - sometimes I just *love* coding y'know?!  */
-static void shmcb_get_header(void *shm_mem, SHMCBHeader **header)
-{
-    *header = (SHMCBHeader *)shm_mem;
-    return;
-}
-
-/* This is what populates our "interesting" structures. Given a
- * pointer to the header, and an index into the appropriate
- * division (this must have already been masked using the
- * division_mask by the caller!), we can populate the provided
- * SHMCBQueue and SHMCBCache structures with values and
- * pointers to the underlying shared memory. Upon returning
- * (if not FALSE), the caller can meddle with the pointer
- * values and they will map into the shared-memory directly,
- * as such there's no need to "free" or "set" the Queue or
- * Cache values, they were themselves references to the *real*
- * data. */
-static BOOL shmcb_get_division(
-    SHMCBHeader *header, SHMCBQueue *queue,
-    SHMCBCache *cache, unsigned int idx)
-{
-    unsigned char *pQueue;
-    unsigned char *pCache;
-
-    /* bounds check */
-    if (idx > (unsigned int) header->division_mask)
-        return FALSE;
 
-    /* Locate the blocks of memory storing the corresponding data */
-    pQueue = ((unsigned char *) header) + header->division_offset +
-        (idx * header->division_size);
-    pCache = pQueue + header->queue_size;
+    /* If there are entries to expire, ditch them first. */
+    shmcb_subcache_expire(s, header, subcache);
 
-    /* Populate the structures with appropriate pointers */
-    queue->first_pos = (unsigned int *) pQueue;
-
-    /* Our structures stay packed, no matter what the system's
-     * data-alignment regime is. */
-    queue->pos_count = (unsigned int *) (pQueue + sizeof(unsigned int));
-    queue->indexes = (SHMCBIndex *) (pQueue + (2 * sizeof(unsigned int)));
-    cache->first_pos = (unsigned int *) pCache;
-    cache->pos_count = (unsigned int *) (pCache + sizeof(unsigned int));
-    cache->data = (unsigned char *) (pCache + (2 * sizeof(unsigned int)));
-    queue->header = cache->header = header;
-
-    return TRUE;
-}
-
-/* This returns a pointer to the piece of shared memory containing
- * a specified 'Index'. SHMCBIndex, like SHMCBHeader, is a fixed
- * width non-referencing structure of primitive types that can be
- * cast onto the corresponding block of shared memory. Thus, by
- * returning a cast pointer to that section of shared memory, the
- * caller can read and write values to and from the "structure" and
- * they are actually reading and writing the underlying shared
- * memory. */
-static SHMCBIndex *shmcb_get_index(
-    const SHMCBQueue *queue, unsigned int idx)
-{
-    /* bounds check */
-    if (idx > queue->header->index_num)
-        return NULL;
+    /* Loop until there is enough space to insert */
+    if (header->subcache_data_size - subcache->data_used < data_len
+        || subcache->idx_used == header->index_num) {
+        unsigned int loop = 0;
 
-    /* Return a pointer to the index. NB: I am being horribly pendantic
-     * here so as to avoid any potential data-alignment assumptions being
-     * placed on the pointer arithmetic by the compiler (sigh). */
-    return (SHMCBIndex *)(((unsigned char *) queue->indexes) +
-                          (idx * sizeof(SHMCBIndex)));
-}
-
-/* This functions rolls expired cache (and index) entries off the front
- * of the cyclic buffers in a division. The function returns the number
- * of expired sessions. */
-static unsigned int shmcb_expire_division(
-    server_rec *s, SHMCBQueue *queue, SHMCBCache *cache)
-{
-    SHMCBIndex *idx;
-    time_t now;
-    unsigned int loop, index_num, pos_count, new_pos;
-    SHMCBHeader *header;
-
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "entering shmcb_expire_division");
-
-    /* We must calculate num and space ourselves based on expiry times. */
-    now = time(NULL);
-    loop = 0;
-    new_pos = shmcb_get_safe_uint(queue->first_pos);
-
-    /* Cache useful values */
-    header = queue->header;
-    index_num = header->index_num;
-    pos_count = shmcb_get_safe_uint(queue->pos_count);
-    while (loop < pos_count) {
-        idx = shmcb_get_index(queue, new_pos);
-        if (shmcb_get_safe_time(&(idx->expires)) > now)
-            /* it hasn't expired yet, we're done iterating */
-            break;
-        /* This one should be expired too. Shift to the next entry. */
-        loop++;
-        new_pos = shmcb_cyclic_increment(index_num, new_pos, 1);
-    }
-
-    /* Find the new_offset and make the expiries happen. */
-    if (loop > 0) {
-        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                     "will be expiring %u sessions", loop);
-        /* We calculate the new_offset by "peeking" (or in the
-         * case it's the last entry, "sneaking" ;-). */
-        if (loop == pos_count) {
-            /* We are expiring everything! This is easy to do... */
-            shmcb_set_safe_uint(queue->pos_count, 0);
-            shmcb_set_safe_uint(cache->pos_count, 0);
-        }
-        else {
-            /* The Queue is easy to adjust */
-            shmcb_set_safe_uint(queue->pos_count,
-                               shmcb_get_safe_uint(queue->pos_count) - loop);
-            shmcb_set_safe_uint(queue->first_pos, new_pos);
-            /* peek to the start of the next session */
-            idx = shmcb_get_index(queue, new_pos);
-            /* We can use shmcb_cyclic_space because we've guaranteed
-             * we don't fit the ambiguous full/empty case. */
-            shmcb_set_safe_uint(cache->pos_count,
-                               shmcb_get_safe_uint(cache->pos_count) -
-                               shmcb_cyclic_space(header->cache_data_size,
-                                                  shmcb_get_safe_uint(cache->first_pos),
-                                                  shmcb_get_safe_uint(&(idx->offset))));
-            shmcb_set_safe_uint(cache->first_pos, shmcb_get_safe_uint(&(idx->offset)));
-        }
+        idx = SHMCB_INDEX(subcache, subcache->idx_pos);
         ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                     "we now have %u sessions",
-                     shmcb_get_safe_uint(queue->pos_count));
-    }
-    header->num_expiries += loop;
-    return loop;
-}
-
-/* Inserts a new encoded session into a queue/cache pair - expiring
- * (early or otherwise) any leading sessions as necessary to ensure
- * there is room. An error return (FALSE) should only happen in the
- * event of surreal values being passed on, or ridiculously small
- * cache sizes. NB: For tracing purposes, this function is also given
- * the server_rec to allow "ssl_log()". */
-static BOOL shmcb_insert_encoded_session(
-    server_rec *s, SHMCBQueue * queue,
-    SHMCBCache * cache,
-    unsigned char *encoded,
-    unsigned int encoded_len,
-    unsigned char *session_id,
-    time_t expiry_time)
-{
-    SHMCBHeader *header;
-    SHMCBIndex *idx = NULL;
-    unsigned int gap, new_pos, loop, new_offset;
-    int need;
-
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "entering shmcb_insert_encoded_session, "
-                 "*queue->pos_count = %u",
-                 shmcb_get_safe_uint(queue->pos_count));
-
-    /* If there's entries to expire, ditch them first thing. */
-    shmcb_expire_division(s, queue, cache);
-    header = cache->header;
-    gap = header->cache_data_size - shmcb_get_safe_uint(cache->pos_count);
-    if (gap < encoded_len) {
-        new_pos = shmcb_get_safe_uint(queue->first_pos);
-        loop = 0;
-        need = (int) encoded_len - (int) gap;
-        while ((need > 0) && (loop + 1 < shmcb_get_safe_uint(queue->pos_count))) {
-            new_pos = shmcb_cyclic_increment(header->index_num, new_pos, 1);
-            loop += 1;
-            idx = shmcb_get_index(queue, new_pos);
-            need = (int) encoded_len - (int) gap -
-                shmcb_cyclic_space(header->cache_data_size,
-                                   shmcb_get_safe_uint(cache->first_pos),
-                                   shmcb_get_safe_uint(&(idx->offset)));
-        }
-        if (loop > 0) {
-            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                         "about to scroll %u sessions from %u",
-                         loop, shmcb_get_safe_uint(queue->pos_count));
-            /* We are removing "loop" items from the cache. */
-            shmcb_set_safe_uint(cache->pos_count,
-                                shmcb_get_safe_uint(cache->pos_count) -
-                                shmcb_cyclic_space(header->cache_data_size,
-                                                   shmcb_get_safe_uint(cache->first_pos),
-                                                   shmcb_get_safe_uint(&(idx->offset))));
-            shmcb_set_safe_uint(cache->first_pos, shmcb_get_safe_uint(&(idx->offset)));
-            shmcb_set_safe_uint(queue->pos_count, shmcb_get_safe_uint(queue->pos_count) - loop);
-            shmcb_set_safe_uint(queue->first_pos, new_pos);
-            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                         "now only have %u sessions",
-                         shmcb_get_safe_uint(queue->pos_count));
-            /* Update the stats!!! */
-            header->num_scrolled += loop;
-        }
-    }
+                     "about to force-expire, subcache: idx_used=%d, "
+                     "data_used=%d", subcache->idx_used, subcache->data_used);
+        do {
+            SHMCBIndex *idx2;
+
+            /* Adjust the indexes by one */
+            subcache->idx_pos = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, 1,
+                                                       header->index_num);
+            subcache->idx_used--;
+            if (!subcache->idx_used) {
+                /* There's nothing left */
+                subcache->data_used = 0;
+                break;
+            }
+            /* Adjust the data */
+            idx2 = SHMCB_INDEX(subcache, subcache->idx_pos);
+            subcache->data_used -= SHMCB_CYCLIC_SPACE(idx->data_pos, idx2->data_pos,
+                                                      header->subcache_data_size);
+            subcache->data_pos = idx2->data_pos;
+            /* Stats */
+            header->stat_scrolled++;
+            /* Loop admin */
+            idx = idx2;
+            loop++;
+        } while (header->subcache_data_size - subcache->data_used < data_len);
 
-    /* probably unecessary checks, but I'll leave them until this code
-     * is verified. */
-    if (shmcb_get_safe_uint(cache->pos_count) + encoded_len >
-        header->cache_data_size) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "shmcb_insert_encoded_session internal error");
-        return FALSE;
-    }
-    if (shmcb_get_safe_uint(queue->pos_count) == header->index_num) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "shmcb_insert_encoded_session internal error");
-        return FALSE;
+        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                     "finished force-expire, subcache: idx_used=%d, "
+                     "data_used=%d", subcache->idx_used, subcache->data_used);
     }
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "we have %u bytes and %u indexes free - enough",
-                 header->cache_data_size -
-                 shmcb_get_safe_uint(cache->pos_count), header->index_num -
-                 shmcb_get_safe_uint(queue->pos_count));
-
 
     /* HERE WE ASSUME THAT THE NEW SESSION SHOULD GO ON THE END! I'M NOT
      * CHECKING WHETHER IT SHOULD BE GENUINELY "INSERTED" SOMEWHERE.
@@ -1129,220 +594,145 @@ static BOOL shmcb_insert_encoded_session(
      * would make this stuff *MUCH* more efficient. Mind you, it's very
      * efficient right now because I'm ignoring this problem!!!
      */
-
-    /* Increment to the first unused byte */
-    new_offset = shmcb_cyclic_increment(header->cache_data_size,
-                                        shmcb_get_safe_uint(cache->first_pos),
-                                        shmcb_get_safe_uint(cache->pos_count));
-    /* Copy the DER-encoded session into place */
-    shmcb_cyclic_ntoc_memcpy(header->cache_data_size, cache->data,
-                            new_offset, encoded, encoded_len);
-    /* Get the new index that this session is stored in. */
-    new_pos = shmcb_cyclic_increment(header->index_num,
-                                     shmcb_get_safe_uint(queue->first_pos),
-                                     shmcb_get_safe_uint(queue->pos_count));
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "storing in index %u, at offset %u",
-                 new_pos, new_offset);
-    idx = shmcb_get_index(queue, new_pos);
-    if (idx == NULL) {
-        ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                     "shmcb_insert_encoded_session internal error");
-        return FALSE;
-    }
-    shmcb_safe_clear(idx, sizeof(SHMCBIndex));
-    shmcb_set_safe_time(&(idx->expires), expiry_time);
-    shmcb_set_safe_uint(&(idx->offset), new_offset);
-
-    /* idx->removed = (unsigned char)0; */ /* Not needed given the memset above. */
-    idx->s_id2 = session_id[1];
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "session_id[0]=%u, idx->s_id2=%u",
-                 session_id[0], session_id[1]);
-
-    /* All that remains is to adjust the cache's and queue's "pos_count"s. */
-    shmcb_set_safe_uint(cache->pos_count,
-                       shmcb_get_safe_uint(cache->pos_count) + encoded_len);
-    shmcb_set_safe_uint(queue->pos_count,
-                       shmcb_get_safe_uint(queue->pos_count) + 1);
-
-    /* And just for good debugging measure ... */
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "leaving now with %u bytes in the cache and %u indexes",
-                 shmcb_get_safe_uint(cache->pos_count),
-                 shmcb_get_safe_uint(queue->pos_count));
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "leaving shmcb_insert_encoded_session");
+    /* Insert the data */
+    new_offset = SHMCB_CYCLIC_INCREMENT(subcache->data_pos, subcache->data_used,
+                                        header->subcache_data_size);
+    shmcb_cyclic_ntoc_memcpy(header->subcache_data_size,
+                             SHMCB_DATA(header, subcache), new_offset,
+                             data, data_len);
+    subcache->data_used += data_len;
+    /* Insert the index */
+    new_idx = SHMCB_CYCLIC_INCREMENT(subcache->idx_pos, subcache->idx_used,
+                                     header->index_num);
+    idx = SHMCB_INDEX(subcache, new_idx);
+    idx->expires = expiry;
+    idx->data_pos = new_offset;
+    idx->data_used = data_len;
+    idx->s_id2 = id[1];
+    idx->removed = 0;
+    subcache->idx_used++;
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "insert happened at idx=%d, data=%d", new_idx, new_offset);
+    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                 "finished insert, subcache: idx_pos/idx_used=%d/%d, "
+                 "data_pos/data_used=%d/%d",
+                 subcache->idx_pos, subcache->idx_used,
+                 subcache->data_pos, subcache->data_used);
     return TRUE;
 }
 
-/* Performs a lookup into a queue/cache pair for a
- * session_id. If found, the session is deserialised
- * and returned, otherwise NULL. */
-static SSL_SESSION *shmcb_lookup_session_id(
-    server_rec *s, SHMCBQueue *queue,
-    SHMCBCache *cache, UCHAR *id,
-    unsigned int idlen)
+static SSL_SESSION *shmcb_subcache_retrieve(server_rec *s, SHMCBHeader *header,
+                                            SHMCBSubcache *subcache, UCHAR *id,
+                                            unsigned int idlen)
 {
-    unsigned char tempasn[SSL_SESSION_MAX_DER];
-    SHMCBIndex *idx;
-    SHMCBHeader *header;
-    SSL_SESSION *pSession = NULL;
-    unsigned int curr_pos, loop, count;
-    MODSSL_D2I_SSL_SESSION_CONST unsigned char *ptr;
-    time_t now;
-
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "entering shmcb_lookup_session_id");
-
-    /* If there are entries to expire, ditch them first thing. */
-    shmcb_expire_division(s, queue, cache);
-    now = time(NULL);
-    curr_pos = shmcb_get_safe_uint(queue->first_pos);
-    count = shmcb_get_safe_uint(queue->pos_count);
-    header = queue->header;
-    for (loop = 0; loop < count; loop++) {
-        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                     "loop=%u, count=%u, curr_pos=%u",
-                     loop, count, curr_pos);
-        idx = shmcb_get_index(queue, curr_pos);
-        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                     "idx->s_id2=%u, id[1]=%u, offset=%u",
-                idx->s_id2, id[1], shmcb_get_safe_uint(&(idx->offset)));
-        /* Only look into the session further if;
-         * (a) the second byte of the session_id matches,
-         * (b) the "removed" flag isn't set,
-         * (c) the session hasn't expired yet.
-         * We do (c) like this so that it saves us having to
-         * do natural expiries ... naturally expired sessions
-         * scroll off the front anyway when the cache is full and
-         * "rotating", the only real issue that remains is the
-         * removal or disabling of forcibly killed sessions. */
-        if ((idx->s_id2 == id[1]) && !idx->removed &&
-            (shmcb_get_safe_time(&(idx->expires)) > now)) {
-            unsigned int session_id_length;
-            unsigned char *session_id;
+    unsigned int pos;
+    unsigned int loop = 0;
+
+    /* If there are entries to expire, ditch them first. */
+    shmcb_subcache_expire(s, header, subcache);
+    pos = subcache->idx_pos;
+
+    while (loop < subcache->idx_used) {
+        SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
+
+        /* Only consider 'idx' if;
+         * (a) the s_id2 byte matches
+         * (b) the "removed" flag isn't set.
+         */
+        if ((idx->s_id2 == id[1]) && !idx->removed) {
+            SSL_SESSION *pSession;
+            unsigned char *s_id;
+            unsigned int s_idlen;
+            unsigned char tempasn[SSL_SESSION_MAX_DER];
+            MODSSL_D2I_SSL_SESSION_CONST unsigned char *ptr = tempasn;
 
             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                         "at index %u, found possible session match",
-                         curr_pos);
-            shmcb_cyclic_cton_memcpy(header->cache_data_size,
-                                     tempasn, cache->data,
-                                     shmcb_get_safe_uint(&(idx->offset)),
-                                     SSL_SESSION_MAX_DER);
-            ptr = tempasn;
-            pSession = d2i_SSL_SESSION(NULL, &ptr, SSL_SESSION_MAX_DER);
-            session_id_length = SSL_SESSION_get_session_id_length(pSession);
-            session_id = SSL_SESSION_get_session_id(pSession);
-
-            if (pSession == NULL) {
+                         "possible match at idx=%d, data=%d", pos, idx->data_pos);
+            /* Copy the data */
+            shmcb_cyclic_cton_memcpy(header->subcache_data_size,
+                                     tempasn, SHMCB_DATA(header, subcache),
+                                     idx->data_pos, idx->data_used);
+            /* Decode the session */
+            pSession = d2i_SSL_SESSION(NULL, &ptr, idx->data_used);
+            if (!pSession) {
                 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                             "scach2_lookup_session_id internal error");
+                             "shmcb_subcache_retrieve internal error");
                 return NULL;
             }
-            if ((session_id_length == idlen) &&
-                (memcmp(session_id, id, idlen) == 0)) {
+            s_id = SSL_SESSION_get_session_id(pSession);
+            s_idlen = SSL_SESSION_get_session_id_length(pSession);
+            if (s_idlen == idlen && memcmp(s_id, id, idlen) == 0) {
+                /* Found the matching session */
                 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                             "a match!");
+                             "shmcb_subcache_retrieve returning matching session");
                 return pSession;
             }
-            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                         "not a match");
             SSL_SESSION_free(pSession);
-            pSession = NULL;
         }
-        curr_pos = shmcb_cyclic_increment(header->index_num, curr_pos, 1);
+        /* Increment */
+        loop++;
+        pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
     }
+
     ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "no matching sessions were found");
+                 "shmcb_subcache_retrieve found no match");
     return NULL;
 }
 
-static BOOL shmcb_remove_session_id(
-    server_rec *s, SHMCBQueue *queue,
-    SHMCBCache *cache, UCHAR *id, unsigned int idlen)
+static BOOL shmcb_subcache_remove(server_rec *s, SHMCBHeader *header,
+                                  SHMCBSubcache *subcache,
+                                  UCHAR *id, unsigned int idlen)
 {
-    unsigned char tempasn[SSL_SESSION_MAX_DER];
-    SSL_SESSION *pSession = NULL;
-    SHMCBIndex *idx;
-    SHMCBHeader *header;
-    unsigned int curr_pos, loop, count;
-    MODSSL_D2I_SSL_SESSION_CONST unsigned char *ptr;
+    unsigned int pos;
+    unsigned int loop = 0;
     BOOL to_return = FALSE;
 
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "entering shmcb_remove_session_id");
-
-    /* If there's entries to expire, ditch them first thing. */
-    /* shmcb_expire_division(s, queue, cache); */
-
-    /* Regarding the above ... hmmm ... I know my expiry code is slightly
-     * "faster" than all this remove stuff ... but if the higher level
-     * code calls a "remove" operation (and this *only* seems to happen
-     * when it has spotted an expired session before we had a chance to)
-     * then it should get credit for a remove (stats-wise). Also, in the
-     * off-chance that the server *requests* a renegotiate and wants to
-     * wipe the session clean we should give that priority over our own
-     * routine expiry handling. So I've moved the expiry check to *after*
-     * this general remove stuff. */
-    curr_pos = shmcb_get_safe_uint(queue->first_pos);
-    count = shmcb_get_safe_uint(queue->pos_count);
-    header = cache->header;
-    for (loop = 0; loop < count; loop++) {
-        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                     "loop=%u, count=%u, curr_pos=%u",
-                loop, count, curr_pos);
-        idx = shmcb_get_index(queue, curr_pos);
-        ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                     "idx->s_id2=%u, id[1]=%u", idx->s_id2,
-                id[1]);
-        /* Only look into the session further if the second byte of the
-         * session_id matches. */
-        if (idx->s_id2 == id[1]) {
-            unsigned int session_id_length;
-            unsigned char *session_id;
+    /* Unlike the others, we don't do an expire-run first. This is to keep
+     * consistent statistics where a "remove" operation may actually be the
+     * higher layer spotting an expiry issue prior to us. Our caller is
+     * handling stats, so a failure return would be inconsistent if the
+     * intended session was in fact removed by an expiry run. */
+
+    pos = subcache->idx_pos;
+    while (!to_return && (loop < subcache->idx_used)) {
+        SHMCBIndex *idx = SHMCB_INDEX(subcache, pos);
+        /* Only consider 'idx' if the s_id2 byte matches and it's not already
+         * removed - easiest way to avoid costly ASN decodings. */
+        if ((idx->s_id2 == id[1]) && !idx->removed) {
+            SSL_SESSION *pSession;
+            unsigned char *s_id;
+            unsigned int s_idlen;
+            unsigned char tempasn[SSL_SESSION_MAX_DER];
+            MODSSL_D2I_SSL_SESSION_CONST unsigned char *ptr = tempasn;
 
             ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                         "at index %u, found possible "
-                         "session match", curr_pos);
-            shmcb_cyclic_cton_memcpy(header->cache_data_size,
-                                     tempasn, cache->data,
-                                     shmcb_get_safe_uint(&(idx->offset)),
-                                     SSL_SESSION_MAX_DER);
-            ptr = tempasn;
-            pSession = d2i_SSL_SESSION(NULL, &ptr, SSL_SESSION_MAX_DER);
-            if (pSession == NULL) {
+                         "possible match at idx=%d, data=%d", pos, idx->data_pos);
+            /* Copy the data */
+            shmcb_cyclic_cton_memcpy(header->subcache_data_size,
+                                     tempasn, SHMCB_DATA(header, subcache),
+                                     idx->data_pos, idx->data_used);
+            /* Decode the session */
+            pSession = d2i_SSL_SESSION(NULL, &ptr, idx->data_used);
+            if (!pSession) {
                 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
-                             "shmcb_remove_session_id, internal error");
-                goto end;
+                             "shmcb_subcache_remove internal error");
+                return FALSE;
             }
-            session_id_length = SSL_SESSION_get_session_id_length(pSession);
-            session_id = SSL_SESSION_get_session_id(pSession);
-
-            if ((session_id_length == idlen)
-                 && (memcmp(id, session_id, idlen) == 0)) {
-                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                            "a match!");
-                /* Scrub out this session "quietly" */
-                idx->removed = (unsigned char) 1;
-                SSL_SESSION_free(pSession);
+            s_id = SSL_SESSION_get_session_id(pSession);
+            s_idlen = SSL_SESSION_get_session_id_length(pSession);
+            if (s_idlen == idlen && memcmp(s_id, id, idlen) == 0) {
+                /* Found the matching session, remove it quietly. */
+                idx->removed = 1;
                 to_return = TRUE;
-                goto end;
+                ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
+                             "shmcb_subcache_remove removing matching session");
             }
-            ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                         "not a match");
             SSL_SESSION_free(pSession);
-            pSession = NULL;
         }
-        curr_pos = shmcb_cyclic_increment(header->index_num, curr_pos, 1);
+        /* Increment */
+        loop++;
+        pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num);
     }
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "no matching sessions were found");
 
-    /* If there's entries to expire, ditch them now. */
-    shmcb_expire_division(s, queue, cache);
-end:
-    ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s,
-                 "leaving shmcb_remove_session_id");
     return to_return;
 }