/* 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) {
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;
} 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
};
Messung V0.5 in Prozent C=91 H=95 G=92
¤ Dauer der Verarbeitung: 0.30 Sekunden
(vorverarbeitet am 2026-09-29)
¤
*© Formatika GbR, Deutschland