diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:50 +0000 |
commit | fe39ffb8b90ae4e002ed73fe98617cd590abb467 (patch) | |
tree | b80e5956907d8aeaaffe4e4f0c068c0e6157ce8b /modules/cache/mod_socache_shmcb.c | |
parent | Initial commit. (diff) | |
download | apache2-upstream.tar.xz apache2-upstream.zip |
Adding upstream version 2.4.56.upstream/2.4.56upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | modules/cache/mod_socache_shmcb.c | 1087 |
1 files changed, 1087 insertions, 0 deletions
diff --git a/modules/cache/mod_socache_shmcb.c b/modules/cache/mod_socache_shmcb.c new file mode 100644 index 0000000..4727961 --- /dev/null +++ b/modules/cache/mod_socache_shmcb.c @@ -0,0 +1,1087 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "httpd.h" +#include "http_log.h" +#include "http_request.h" +#include "http_protocol.h" +#include "http_config.h" +#include "mod_status.h" + +#include "apr.h" +#include "apr_strings.h" +#include "apr_time.h" +#include "apr_shm.h" +#define APR_WANT_STRFUNC +#include "apr_want.h" +#include "apr_general.h" + +#if APR_HAVE_LIMITS_H +#include <limits.h> +#endif + +#include "ap_socache.h" + +/* XXX Unfortunately, there are still many unsigned ints in use here, so we + * XXX cannot allow more than UINT_MAX. Since some of the ints are exposed in + * XXX public interfaces, a simple search and replace is not enough. + * XXX It should be possible to extend that so that the total cache size can + * XXX be APR_SIZE_MAX and only the object size needs to be smaller than + * XXX UINT_MAX. + */ +#define SHMCB_MAX_SIZE (UINT_MAX<APR_SIZE_MAX ? UINT_MAX : APR_SIZE_MAX) + +#define DEFAULT_SHMCB_PREFIX "socache-shmcb-" + +#define DEFAULT_SHMCB_SUFFIX ".cache" + +#define ALIGNED_HEADER_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBHeader)) +#define ALIGNED_SUBCACHE_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBSubcache)) +#define ALIGNED_INDEX_SIZE APR_ALIGN_DEFAULT(sizeof(SHMCBIndex)) + +/* + * Header structure - the start of the shared-mem segment + */ +typedef struct { + /* Stats for cache operations */ + unsigned long stat_stores; + unsigned long stat_replaced; + 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; + /* 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; + +/* + * 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 */ + apr_time_t expires; + /* 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; + /* length of the used data which contains the id */ + unsigned int id_len; + /* Used to mark explicitly-removed socache entries */ + unsigned char removed; +} SHMCBIndex; + +struct ap_socache_instance_t { + apr_pool_t *pool; + const char *data_file; + apr_size_t shm_size; + apr_shm_t *shm; + SHMCBHeader *header; +}; + +/* The SHM data segment is of fixed size and stores data as follows. + * + * [ SHMCBHeader | Subcaches ] + * + * The SHMCBHeader header structure stores metadata concerning the + * cache and the contained subcaches. + * + * Subcaches is a hash table of header->subcache_num SHMCBSubcache + * structures. The hash table is indexed by SHMCB_MASK(id). Each + * SHMCBSubcache structure has a fixed size (header->subcache_size), + * which is determined at creation time, and looks like the following: + * + * [ SHMCBSubcache | Indexes | Data ] + * + * Each subcache is prefixed by the SHMCBSubcache structure. + * + * The subcache's "Data" segment is a single cyclic data buffer, of + * total size header->subcache_data_size; data inside is referenced + * using byte offsets. The offset marking the beginning of the cyclic + * buffer is subcache->data_pos; the buffer's length is + * subcache->data_used. + * + * "Indexes" is an array of header->index_num SHMCBIndex structures, + * which is used as a cyclic queue; subcache->idx_pos gives the array + * index of the first in use, subcache->idx_used gives the number in + * use. Both ->idx_* values have a range of [0, header->index_num) + * + * Each in-use SHMCBIndex structure represents a single cached object. + * The ID and data segment are stored consecutively in the subcache's + * cyclic data buffer. The "Data" segment can thus be seen to + * look like this, for example + * + * offset: [ 0 1 2 3 4 5 6 ... + * contents:[ ID1 Data1 ID2 Data2 ID3 ... + * + * where the corresponding indices would look like: + * + * idx1 = { data_pos = 0, data_used = 3, id_len = 1, ...} + * idx2 = { data_pos = 3, data_used = 3, id_len = 1, ...} + * ... + */ + +/* 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)) + \ + ALIGNED_HEADER_SIZE + \ + (num) * ((pHeader)->subcache_size)) + +/* This macro takes a pointer to the header and an 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) + \ + ALIGNED_SUBCACHE_SIZE + \ + (num) * ALIGNED_INDEX_SIZE) + +/* 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) + +/* + * Cyclic functions - assists in "wrap-around"/modulo logic + */ + +/* Addition modulo 'mod' */ +#define SHMCB_CYCLIC_INCREMENT(val,inc,mod) \ + (((val) + (inc)) % (mod)) + +/* Subtraction (or "distance between") modulo 'mod' */ +#define SHMCB_CYCLIC_SPACE(val1,val2,mod) \ + ((val2) >= (val1) ? ((val2) - (val1)) : \ + ((val2) + (mod) - (val1))) + +/* A "normal-to-cyclic" memcpy. */ +static void shmcb_cyclic_ntoc_memcpy(unsigned int buf_size, unsigned char *data, + unsigned int dest_offset, const unsigned char *src, + unsigned int src_len) +{ + 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); + } +} + +/* A "cyclic-to-normal" memcpy. */ +static void shmcb_cyclic_cton_memcpy(unsigned int buf_size, unsigned char *dest, + const unsigned char *data, unsigned int src_offset, + unsigned int src_len) +{ + 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); + } +} + +/* A memcmp against a cyclic data buffer. Compares SRC of length + * SRC_LEN against the contents of cyclic buffer DATA (which is of + * size BUF_SIZE), starting at offset DEST_OFFSET. Got that? Good. */ +static int shmcb_cyclic_memcmp(unsigned int buf_size, unsigned char *data, + unsigned int dest_offset, + const unsigned char *src, + unsigned int src_len) +{ + if (dest_offset + src_len < buf_size) + /* It be compared all in one go */ + return memcmp(data + dest_offset, src, src_len); + else { + /* Compare the two splits */ + int diff; + + diff = memcmp(data + dest_offset, src, buf_size - dest_offset); + if (diff) { + return diff; + } + return memcmp(data, src + buf_size - dest_offset, + src_len + dest_offset - buf_size); + } +} + + +/* Prototypes for low-level subcache operations */ +static void shmcb_subcache_expire(server_rec *, SHMCBHeader *, SHMCBSubcache *, + apr_time_t); +/* Returns zero on success, non-zero on failure. */ +static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header, + SHMCBSubcache *subcache, + unsigned char *data, unsigned int data_len, + const unsigned char *id, unsigned int id_len, + apr_time_t expiry); +/* Returns zero on success, non-zero on failure. */ +static int shmcb_subcache_retrieve(server_rec *, SHMCBHeader *, SHMCBSubcache *, + const unsigned char *id, unsigned int idlen, + unsigned char *data, unsigned int *datalen); +/* Returns zero on success, non-zero on failure. */ +static int shmcb_subcache_remove(server_rec *, SHMCBHeader *, SHMCBSubcache *, + const unsigned char *, unsigned int); + +/* Returns result of the (iterator)() call, zero is success (continue) */ +static apr_status_t shmcb_subcache_iterate(ap_socache_instance_t *instance, + server_rec *s, + void *userctx, + SHMCBHeader *header, + SHMCBSubcache *subcache, + ap_socache_iterator_t *iterator, + unsigned char **buf, + apr_size_t *buf_len, + apr_pool_t *pool, + apr_time_t now); + +/* + * High-Level "handlers" as per ssl_scache.c + * subcache internals are deferred to shmcb_subcache_*** functions lower down + */ + +static const char *socache_shmcb_create(ap_socache_instance_t **context, + const char *arg, + apr_pool_t *tmp, apr_pool_t *p) +{ + ap_socache_instance_t *ctx; + char *path, *cp, *cp2; + + /* Allocate the context. */ + *context = ctx = apr_pcalloc(p, sizeof *ctx); + ctx->pool = p; + + ctx->shm_size = 1024*512; /* 512KB */ + + if (!arg || *arg == '\0') { + /* Use defaults. */ + return NULL; + } + + ctx->data_file = path = ap_server_root_relative(p, arg); + + cp = strrchr(path, '('); + cp2 = path + strlen(path) - 1; + if (cp) { + char *endptr; + if (*cp2 != ')') { + return "Invalid argument: no closing parenthesis or cache size " + "missing after pathname with parenthesis"; + } + *cp++ = '\0'; + *cp2 = '\0'; + + + ctx->shm_size = strtol(cp, &endptr, 10); + if (endptr != cp2) { + return "Invalid argument: cache size not numerical"; + } + + if (ctx->shm_size < 8192) { + return "Invalid argument: size has to be >= 8192 bytes"; + + } + + if (ctx->shm_size >= SHMCB_MAX_SIZE) { + return apr_psprintf(tmp, "Invalid argument: size has " + "to be < %" APR_SIZE_T_FMT " bytes on this platform", + SHMCB_MAX_SIZE); + } + } + else if (cp2 >= path && *cp2 == ')') { + return "Invalid argument: no opening parenthesis"; + } + + return NULL; +} + +static apr_status_t socache_shmcb_cleanup(void *arg) +{ + ap_socache_instance_t *ctx = arg; + if (ctx->shm) { + apr_shm_destroy(ctx->shm); + ctx->shm = NULL; + } + return APR_SUCCESS; +} + +static apr_status_t socache_shmcb_init(ap_socache_instance_t *ctx, + const char *namespace, + const struct ap_socache_hints *hints, + 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; + apr_size_t avg_obj_size, avg_id_len; + + /* Create shared memory segment */ + if (ctx->data_file == NULL) { + const char *path = apr_pstrcat(p, DEFAULT_SHMCB_PREFIX, namespace, + DEFAULT_SHMCB_SUFFIX, NULL); + + ctx->data_file = ap_runtime_dir_relative(p, path); + } + + /* Use anonymous shm by default, fall back on name-based. */ + rv = apr_shm_create(&ctx->shm, ctx->shm_size, NULL, p); + if (APR_STATUS_IS_ENOTIMPL(rv)) { + /* If anon shm isn't supported, fail if no named file was + * configured successfully; the ap_server_root_relative call + * above will return NULL for invalid paths. */ + if (ctx->data_file == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00818) + "Could not use anonymous shm for '%s' cache", + namespace); + ctx->shm = NULL; + return APR_EINVAL; + } + + /* For a name-based segment, remove it first in case of a + * previous unclean shutdown. */ + apr_shm_remove(ctx->data_file, p); + + rv = apr_shm_create(&ctx->shm, ctx->shm_size, ctx->data_file, p); + } + + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_ERR, rv, s, APLOGNO(00819) + "Could not allocate shared memory segment for shmcb " + "socache"); + ctx->shm = NULL; + return rv; + } + apr_pool_cleanup_register(ctx->pool, ctx, socache_shmcb_cleanup, + apr_pool_cleanup_null); + + shm_segment = apr_shm_baseaddr_get(ctx->shm); + shm_segsize = apr_shm_size_get(ctx->shm); + if (shm_segsize < (5 * ALIGNED_HEADER_SIZE)) { + /* the segment is ridiculously small, bail out */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00820) + "shared memory segment too small"); + return APR_ENOSPC; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00821) + "shmcb_init allocated %" APR_SIZE_T_FMT + " bytes of shared memory", + shm_segsize); + /* Discount the header */ + shm_segsize -= ALIGNED_HEADER_SIZE; + /* Select index size based on average object size hints, if given. */ + avg_obj_size = hints && hints->avg_obj_size ? hints->avg_obj_size : 150; + avg_id_len = hints && hints->avg_id_len ? hints->avg_id_len : 30; + num_idx = (shm_segsize) / (avg_obj_size + avg_id_len); + 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, APLOGNO(00822) + "for %" APR_SIZE_T_FMT " bytes (%" APR_SIZE_T_FMT + " including header), recommending %u subcaches, " + "%u indexes each", shm_segsize, + shm_segsize + ALIGNED_HEADER_SIZE, + num_subcache, num_idx); + if (num_idx < 5) { + /* we're still too small, bail out */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00823) + "shared memory segment too small"); + return APR_ENOSPC; + } + /* OK, we're sorted */ + ctx->header = header = shm_segment; + header->stat_stores = 0; + header->stat_replaced = 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. */ + header->subcache_size = (size_t)(shm_segsize / num_subcache); + if (header->subcache_size != APR_ALIGN_DEFAULT(header->subcache_size)) { + header->subcache_size = APR_ALIGN_DEFAULT(header->subcache_size) - + APR_ALIGN_DEFAULT(1); + } + header->subcache_data_offset = ALIGNED_SUBCACHE_SIZE + + num_idx * ALIGNED_INDEX_SIZE; + 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, APLOGNO(00824) + "shmcb_init_memory choices follow"); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00825) + "subcache_num = %u", header->subcache_num); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00826) + "subcache_size = %u", header->subcache_size); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00827) + "subcache_data_offset = %u", header->subcache_data_offset); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00828) + "subcache_data_size = %u", header->subcache_data_size); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00829) + "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, APLOGNO(00830) + "Shared memory socache initialised"); + /* Success ... */ + + return APR_SUCCESS; +} + +static void socache_shmcb_destroy(ap_socache_instance_t *ctx, server_rec *s) +{ + if (ctx) { + apr_pool_cleanup_run(ctx->pool, ctx, socache_shmcb_cleanup); + } +} + +static apr_status_t socache_shmcb_store(ap_socache_instance_t *ctx, + server_rec *s, const unsigned char *id, + unsigned int idlen, apr_time_t expiry, + unsigned char *encoded, + unsigned int len_encoded, + apr_pool_t *p) +{ + SHMCBHeader *header = ctx->header; + SHMCBSubcache *subcache = SHMCB_MASK(header, id); + int tryreplace; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00831) + "socache_shmcb_store (0x%02x -> subcache %d)", + SHMCB_MASK_DBG(header, id)); + /* XXX: Says who? Why shouldn't this be acceptable, or padded if not? */ + if (idlen < 4) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00832) "unusably short id provided " + "(%u bytes)", idlen); + return APR_EINVAL; + } + tryreplace = shmcb_subcache_remove(s, header, subcache, id, idlen); + if (shmcb_subcache_store(s, header, subcache, encoded, + len_encoded, id, idlen, expiry)) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00833) + "can't store an socache entry!"); + return APR_ENOSPC; + } + if (tryreplace == 0) { + header->stat_replaced++; + } + else { + header->stat_stores++; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00834) + "leaving socache_shmcb_store successfully"); + return APR_SUCCESS; +} + +static apr_status_t socache_shmcb_retrieve(ap_socache_instance_t *ctx, + server_rec *s, + const unsigned char *id, unsigned int idlen, + unsigned char *dest, unsigned int *destlen, + apr_pool_t *p) +{ + SHMCBHeader *header = ctx->header; + SHMCBSubcache *subcache = SHMCB_MASK(header, id); + int rv; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00835) + "socache_shmcb_retrieve (0x%02x -> subcache %d)", + SHMCB_MASK_DBG(header, id)); + + /* Get the entry corresponding to the id, if it exists. */ + rv = shmcb_subcache_retrieve(s, header, subcache, id, idlen, + dest, destlen); + if (rv == 0) + header->stat_retrieves_hit++; + else + header->stat_retrieves_miss++; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00836) + "leaving socache_shmcb_retrieve successfully"); + + return rv == 0 ? APR_SUCCESS : APR_NOTFOUND; +} + +static apr_status_t socache_shmcb_remove(ap_socache_instance_t *ctx, + server_rec *s, const unsigned char *id, + unsigned int idlen, apr_pool_t *p) +{ + SHMCBHeader *header = ctx->header; + SHMCBSubcache *subcache = SHMCB_MASK(header, id); + apr_status_t rv; + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00837) + "socache_shmcb_remove (0x%02x -> subcache %d)", + SHMCB_MASK_DBG(header, id)); + if (idlen < 4) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00838) "unusably short id provided " + "(%u bytes)", idlen); + return APR_EINVAL; + } + if (shmcb_subcache_remove(s, header, subcache, id, idlen) == 0) { + header->stat_removes_hit++; + rv = APR_SUCCESS; + } else { + header->stat_removes_miss++; + rv = APR_NOTFOUND; + } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00839) + "leaving socache_shmcb_remove successfully"); + + return rv; +} + +static void socache_shmcb_status(ap_socache_instance_t *ctx, + request_rec *r, int flags) +{ + server_rec *s = r->server; + SHMCBHeader *header = ctx->header; + unsigned int loop, total = 0, cache_total = 0, non_empty_subcaches = 0; + apr_time_t idx_expiry, min_expiry = 0, max_expiry = 0; + apr_time_t now = apr_time_now(); + double expiry_total = 0; + int index_pct, cache_pct; + + AP_DEBUG_ASSERT(header->subcache_num > 0); + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00840) "inside shmcb_status"); + /* 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. */ + /* Iterate over the subcaches */ + for (loop = 0; loop < header->subcache_num; loop++) { + SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop); + shmcb_subcache_expire(s, header, subcache, now); + 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->subcache_num); + cache_pct = (100 * cache_total) / (header->subcache_data_size * + header->subcache_num); + /* Generate Output */ + if (!(flags & AP_STATUS_SHORT)) { + ap_rprintf(r, "cache type: <b>SHMCB</b>, shared memory: <b>%" APR_SIZE_T_FMT "</b> " + "bytes, current entries: <b>%d</b><br>", + ctx->shm_size, total); + ap_rprintf(r, "subcaches: <b>%d</b>, indexes per subcache: <b>%d</b><br>", + header->subcache_num, header->index_num); + if (non_empty_subcaches) { + apr_time_t average_expiry = (apr_time_t)(expiry_total / (double)non_empty_subcaches); + ap_rprintf(r, "time left on oldest entries' objects: "); + if (now < average_expiry) + ap_rprintf(r, "avg: <b>%d</b> seconds, (range: %d...%d)<br>", + (int)apr_time_sec(average_expiry - now), + (int)apr_time_sec(min_expiry - now), + (int)apr_time_sec(max_expiry - now)); + else + 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, "total entries stored since starting: <b>%lu</b><br>", + header->stat_stores); + ap_rprintf(r, "total entries replaced since starting: <b>%lu</b><br>", + header->stat_replaced); + ap_rprintf(r, "total entries expired since starting: <b>%lu</b><br>", + header->stat_expiries); + ap_rprintf(r, "total (pre-expiry) entries 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->stat_retrieves_hit, + header->stat_retrieves_miss); + ap_rprintf(r, "total removes since starting: <b>%lu</b> hit, " + "<b>%lu</b> miss<br>", header->stat_removes_hit, + header->stat_removes_miss); + } + else { + ap_rputs("CacheType: SHMCB\n", r); + ap_rprintf(r, "CacheSharedMemory: %" APR_SIZE_T_FMT "\n", + ctx->shm_size); + ap_rprintf(r, "CacheCurrentEntries: %d\n", total); + ap_rprintf(r, "CacheSubcaches: %d\n", header->subcache_num); + ap_rprintf(r, "CacheIndexesPerSubcaches: %d\n", header->index_num); + if (non_empty_subcaches) { + apr_time_t average_expiry = (apr_time_t)(expiry_total / (double)non_empty_subcaches); + if (now < average_expiry) { + ap_rprintf(r, "CacheTimeLeftOldestAvg: %d\n", (int)apr_time_sec(average_expiry - now)); + ap_rprintf(r, "CacheTimeLeftOldestMin: %d\n", (int)apr_time_sec(min_expiry - now)); + ap_rprintf(r, "CacheTimeLeftOldestMax: %d\n", (int)apr_time_sec(max_expiry - now)); + } + } + + ap_rprintf(r, "CacheIndexUsage: %d%%\n", index_pct); + ap_rprintf(r, "CacheUsage: %d%%\n", cache_pct); + ap_rprintf(r, "CacheStoreCount: %lu\n", header->stat_stores); + ap_rprintf(r, "CacheReplaceCount: %lu\n", header->stat_replaced); + ap_rprintf(r, "CacheExpireCount: %lu\n", header->stat_expiries); + ap_rprintf(r, "CacheDiscardCount: %lu\n", header->stat_scrolled); + ap_rprintf(r, "CacheRetrieveHitCount: %lu\n", header->stat_retrieves_hit); + ap_rprintf(r, "CacheRetrieveMissCount: %lu\n", header->stat_retrieves_miss); + ap_rprintf(r, "CacheRemoveHitCount: %lu\n", header->stat_removes_hit); + ap_rprintf(r, "CacheRemoveMissCount: %lu\n", header->stat_removes_miss); + } + ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(00841) "leaving shmcb_status"); +} + +static apr_status_t socache_shmcb_iterate(ap_socache_instance_t *instance, + server_rec *s, void *userctx, + ap_socache_iterator_t *iterator, + apr_pool_t *pool) +{ + SHMCBHeader *header = instance->header; + unsigned int loop; + apr_time_t now = apr_time_now(); + apr_status_t rv = APR_SUCCESS; + apr_size_t buflen = 0; + unsigned char *buf = NULL; + + /* 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. */ + /* Iterate over the subcaches */ + for (loop = 0; loop < header->subcache_num && rv == APR_SUCCESS; loop++) { + SHMCBSubcache *subcache = SHMCB_SUBCACHE(header, loop); + rv = shmcb_subcache_iterate(instance, s, userctx, header, subcache, + iterator, &buf, &buflen, pool, now); + } + return rv; +} + +/* + * Subcache-level cache operations + */ + +static void shmcb_subcache_expire(server_rec *s, SHMCBHeader *header, + SHMCBSubcache *subcache, apr_time_t now) +{ + unsigned int loop = 0, freed = 0, expired = 0; + unsigned int new_idx_pos = subcache->idx_pos; + SHMCBIndex *idx = NULL; + + while (loop < subcache->idx_used) { + idx = SHMCB_INDEX(subcache, new_idx_pos); + if (idx->removed) + freed++; + else if (idx->expires <= now) + expired++; + else + /* not removed and not expired yet, we're done iterating */ + break; + loop++; + new_idx_pos = SHMCB_CYCLIC_INCREMENT(new_idx_pos, 1, header->index_num); + } + if (!loop) + /* Nothing to do */ + return; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00842) + "expiring %u and reclaiming %u removed socache entries", + expired, freed); + 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 += expired; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00843) + "we now have %u socache entries", subcache->idx_used); +} + +static int shmcb_subcache_store(server_rec *s, SHMCBHeader *header, + SHMCBSubcache *subcache, + unsigned char *data, unsigned int data_len, + const unsigned char *id, unsigned int id_len, + apr_time_t expiry) +{ + unsigned int data_offset, new_idx, id_offset; + SHMCBIndex *idx; + unsigned int total_len = id_len + data_len; + + /* Sanity check the input */ + if (total_len > header->subcache_data_size) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, APLOGNO(00844) + "inserting socache entry larger (%d) than subcache data area (%d)", + total_len, header->subcache_data_size); + return -1; + } + + /* First reclaim space from removed and expired records. */ + shmcb_subcache_expire(s, header, subcache, apr_time_now()); + + /* Loop until there is enough space to insert + * XXX: This should first compress out-of-order expiries and + * removed records, and then force-remove oldest-first + */ + if (header->subcache_data_size - subcache->data_used < total_len + || subcache->idx_used == header->index_num) { + unsigned int loop = 0; + + idx = SHMCB_INDEX(subcache, subcache->idx_pos); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00845) + "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 < total_len); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00846) + "finished force-expire, subcache: idx_used=%d, " + "data_used=%d", subcache->idx_used, subcache->data_used); + } + + /* HERE WE ASSUME THAT THE NEW ENTRY SHOULD GO ON THE END! I'M NOT + * CHECKING WHETHER IT SHOULD BE GENUINELY "INSERTED" SOMEWHERE. + * + * We aught to fix that. httpd (never mind third party modules) + * does not promise to perform any processing in date order + * (c.f. FAQ "My log entries are not in date order!") + */ + /* Insert the id */ + id_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), id_offset, + id, id_len); + subcache->data_used += id_len; + /* Insert the data */ + data_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), data_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 = id_offset; + idx->data_used = total_len; + idx->id_len = id_len; + idx->removed = 0; + subcache->idx_used++; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00847) + "insert happened at idx=%d, data=(%u:%u)", new_idx, + id_offset, data_offset); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00848) + "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 0; +} + +static int shmcb_subcache_retrieve(server_rec *s, SHMCBHeader *header, + SHMCBSubcache *subcache, + const unsigned char *id, unsigned int idlen, + unsigned char *dest, unsigned int *destlen) +{ + unsigned int pos; + unsigned int loop = 0; + apr_time_t now = apr_time_now(); + + pos = subcache->idx_pos; + + while (loop < subcache->idx_used) { + SHMCBIndex *idx = SHMCB_INDEX(subcache, pos); + + /* Only consider 'idx' if the id matches, and the "removed" + * flag isn't set, and the record is not expired. + * Check the data length too to avoid a buffer overflow + * in case of corruption, which should be impossible, + * but it's cheap to be safe. */ + if (!idx->removed + && idx->id_len == idlen + && (idx->data_used - idx->id_len) <= *destlen + && shmcb_cyclic_memcmp(header->subcache_data_size, + SHMCB_DATA(header, subcache), + idx->data_pos, id, idx->id_len) == 0) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00849) + "match at idx=%d, data=%d", pos, idx->data_pos); + if (idx->expires > now) { + unsigned int data_offset; + + /* Find the offset of the data segment, after the id */ + data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos, + idx->id_len, + header->subcache_data_size); + + *destlen = idx->data_used - idx->id_len; + + /* Copy out the data */ + shmcb_cyclic_cton_memcpy(header->subcache_data_size, + dest, SHMCB_DATA(header, subcache), + data_offset, *destlen); + + return 0; + } + else { + /* Already stale, quietly remove and treat as not-found */ + idx->removed = 1; + header->stat_expiries++; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00850) + "shmcb_subcache_retrieve discarding expired entry"); + return -1; + } + } + /* Increment */ + loop++; + pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num); + } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00851) + "shmcb_subcache_retrieve found no match"); + return -1; +} + +static int shmcb_subcache_remove(server_rec *s, SHMCBHeader *header, + SHMCBSubcache *subcache, + const unsigned char *id, + unsigned int idlen) +{ + unsigned int pos; + unsigned int loop = 0; + + pos = subcache->idx_pos; + while (loop < subcache->idx_used) { + SHMCBIndex *idx = SHMCB_INDEX(subcache, pos); + + /* Only consider 'idx' if the id matches, and the "removed" + * flag isn't set. */ + if (!idx->removed && idx->id_len == idlen + && shmcb_cyclic_memcmp(header->subcache_data_size, + SHMCB_DATA(header, subcache), + idx->data_pos, id, idx->id_len) == 0) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00852) + "possible match at idx=%d, data=%d", pos, idx->data_pos); + + /* Found the matching entry, remove it quietly. */ + idx->removed = 1; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00853) + "shmcb_subcache_remove removing matching entry"); + return 0; + } + /* Increment */ + loop++; + pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num); + } + + return -1; /* failure */ +} + + +static apr_status_t shmcb_subcache_iterate(ap_socache_instance_t *instance, + server_rec *s, + void *userctx, + SHMCBHeader *header, + SHMCBSubcache *subcache, + ap_socache_iterator_t *iterator, + unsigned char **buf, + apr_size_t *buf_len, + apr_pool_t *pool, + apr_time_t now) +{ + unsigned int pos; + unsigned int loop = 0; + apr_status_t rv; + + pos = subcache->idx_pos; + while (loop < subcache->idx_used) { + SHMCBIndex *idx = SHMCB_INDEX(subcache, pos); + + /* Only consider 'idx' if the "removed" flag isn't set. */ + if (!idx->removed) { + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00854) + "iterating idx=%d, data=%d", pos, idx->data_pos); + if (idx->expires > now) { + unsigned char *id = *buf; + unsigned char *dest; + unsigned int data_offset, dest_len; + apr_size_t buf_req; + + /* Find the offset of the data segment, after the id */ + data_offset = SHMCB_CYCLIC_INCREMENT(idx->data_pos, + idx->id_len, + header->subcache_data_size); + + dest_len = idx->data_used - idx->id_len; + + buf_req = APR_ALIGN_DEFAULT(idx->id_len + 1) + + APR_ALIGN_DEFAULT(dest_len + 1); + + if (buf_req > *buf_len) { + /* Grow to ~150% of this buffer requirement on resize + * always using APR_ALIGN_DEFAULT sized pages + */ + *buf_len = buf_req + APR_ALIGN_DEFAULT(buf_req / 2); + *buf = apr_palloc(pool, *buf_len); + id = *buf; + } + + dest = *buf + APR_ALIGN_DEFAULT(idx->id_len + 1); + + /* Copy out the data, because it's potentially cyclic */ + shmcb_cyclic_cton_memcpy(header->subcache_data_size, id, + SHMCB_DATA(header, subcache), + idx->data_pos, idx->id_len); + id[idx->id_len] = '\0'; + + shmcb_cyclic_cton_memcpy(header->subcache_data_size, dest, + SHMCB_DATA(header, subcache), + data_offset, dest_len); + dest[dest_len] = '\0'; + + rv = iterator(instance, s, userctx, id, idx->id_len, + dest, dest_len, pool); + ap_log_error(APLOG_MARK, APLOG_DEBUG, rv, s, APLOGNO(00855) + "shmcb entry iterated"); + if (rv != APR_SUCCESS) + return rv; + } + else { + /* Already stale, quietly remove and treat as not-found */ + idx->removed = 1; + header->stat_expiries++; + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, s, APLOGNO(00856) + "shmcb_subcache_iterate discarding expired entry"); + } + } + /* Increment */ + loop++; + pos = SHMCB_CYCLIC_INCREMENT(pos, 1, header->index_num); + } + + return APR_SUCCESS; +} + +static const ap_socache_provider_t socache_shmcb = { + "shmcb", + AP_SOCACHE_FLAG_NOTMPSAFE, + socache_shmcb_create, + socache_shmcb_init, + socache_shmcb_destroy, + socache_shmcb_store, + socache_shmcb_retrieve, + socache_shmcb_remove, + socache_shmcb_status, + socache_shmcb_iterate +}; + +static void register_hooks(apr_pool_t *p) +{ + ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, "shmcb", + AP_SOCACHE_PROVIDER_VERSION, + &socache_shmcb); + + /* Also register shmcb under the default provider name. */ + ap_register_provider(p, AP_SOCACHE_PROVIDER_GROUP, + AP_SOCACHE_DEFAULT_PROVIDER, + AP_SOCACHE_PROVIDER_VERSION, + &socache_shmcb); +} + +AP_DECLARE_MODULE(socache_shmcb) = { + STANDARD20_MODULE_STUFF, + NULL, NULL, NULL, NULL, NULL, + register_hooks +}; |