mod_mem_cache.c revision a80dd6ffd7a1484e7f45e4665689bdd84fc97153
/* Copyright 2001-2004 The Apache Software Foundation
*
* Licensed 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
*
*
* 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.
*/
/*
* Rules for managing obj->refcount:
* refcount should be incremented when an object is placed in the cache. Insertion
* of an object into the cache and the refcount increment should happen under
* protection of the sconf->lock.
*
* refcount should be decremented when the object is removed from the cache.
* Object should be removed from the cache and the refcount decremented while
* under protection of the sconf->lock.
*
* refcount should be incremented when an object is retrieved from the cache
* should occur under protection of the sconf->lock
*
* refcount can be atomically decremented w/o protection of the sconf->lock
* by worker threads.
*
* of 0 means the object is not in the cache and no worker threads are accessing
* it.
*/
#define CORE_PRIVATE
#include "mod_cache.h"
#include "cache_pqueue.h"
#include "cache_cache.h"
#include "ap_provider.h"
#include "ap_mpm.h"
#include "apr_thread_mutex.h"
#include <unistd.h>
#endif
#if !APR_HAS_THREADS
#endif
typedef enum {
CACHE_TYPE_FILE = 1,
} cache_type_e;
typedef struct {
char* hdr;
char* val;
typedef struct mem_cache_object {
void *m;
long priority; /**< the priority of this entry */
long total_refs; /**< total number of references this entry has had */
typedef struct {
/* Fields set by config directives */
/* maximum amount of data to buffer on a streamed response where
* we haven't yet seen EOS */
static mem_cache_conf *sconf;
#define DEFAULT_MIN_CACHE_OBJECT_SIZE 0
#define DEFAULT_MAX_CACHE_OBJECT_SIZE 10000
#define DEFAULT_MAX_OBJECT_CNT 1009
#define DEFAULT_MAX_STREAMING_BUFFER_SIZE 100000
#define CACHEFILE_LEN 20
/* Forward declarations */
static int remove_entity(cache_handle_t *h);
static long memcache_get_priority(void*a)
{
}
static void memcache_inc_frequency(void*a)
{
mobj->total_refs++;
}
{
}
static apr_ssize_t memcache_get_pos(void *a)
{
}
static apr_size_t memcache_cache_get_size(void*a)
{
}
/** callback to get the key of a item */
static const char* memcache_cache_get_key(void*a)
{
}
/**
* memcache_cache_free()
* memcache_cache_free is a callback that is only invoked by a thread
* running in cache_insert(). cache_insert() runs under protection
* of sconf->lock. By the time this function has been entered, the cache_object
* has been ejected from the cache. decrement the refcount and if the refcount drops
* to 0, cleanup the cache object.
*/
static void memcache_cache_free(void*a)
{
/* Decrement the refcount to account for the object being ejected
* from the cache. If the refcount is 0, free the object.
*/
}
}
/*
* functions return a 'negative' score since priority queues
* dequeue the object with the highest value first
*/
static long memcache_lru_algorithm(long queue_clock, void *a)
{
/*
* a 'proper' LRU function would just be
* mobj->priority = mobj->total_refs;
*/
}
static long memcache_gdsf_algorithm(long queue_clock, void *a)
{
}
{
/* TODO:
* We desperately need a more efficient way of allocating objects. We're
* making way too many malloc calls to create a fully populated
* cache object...
*/
/* Cleanup the cache_object_t */
}
}
}
}
}
/* Cleanup the mem_cache_object_t */
if (mobj) {
}
#ifdef WIN32
#else
#endif
}
if (mobj->header_out) {
}
if (mobj->err_header_out) {
}
if (mobj->subprocess_env) {
}
}
}
}
}
{
/* If obj->complete is not set, the cache update failed and the
* object needs to be removed from the cache then cleaned up.
* The garbage collector may have ejected the object from the
* cache already, so make sure it is really still in the cache
* before attempting to remove it.
*/
}
}
}
}
/* If the refcount drops to 0, cleanup the cache object */
}
return APR_SUCCESS;
}
{
if (!co) {
return APR_SUCCESS;
}
if (!co->cache_cache) {
return APR_SUCCESS;
}
}
while (obj) {
/* Iterate over the cache and clean up each unreferenced entry */
}
}
/* Cache is empty, free the cache table */
}
return APR_SUCCESS;
}
/*
* TODO: enable directives to be overridden in various containers
*/
{
/* Number of objects in the cache */
/* Size of the cache in bytes */
return sconf;
}
{
if (len == -1) {
/* Caching a streaming response. Assume the response is
* less than or equal to max_streaming_buffer_size. We will
* correct all the cache size counters in store_body once
* we know exactly know how much we are caching.
*/
}
/* Note: cache_insert() will automatically garbage collect
* objects from the cache if the max_cache_size threshold is
* exceeded. This means mod_mem_cache does not need to implement
* max_cache_size checks.
*/
"mem_cache: URL %s failed the size check and will not be cached.",
key);
return DECLINED;
}
if (type_e == CACHE_TYPE_FILE) {
/* CACHE_TYPE_FILE is only valid for local content handled by the
* default handler. Need a better way to check if the file is
* local or not.
*/
if (!r->filename) {
return DECLINED;
}
}
/* Allocate and initialize cache_object_t */
if (!obj) {
return DECLINED;
}
return DECLINED;
}
/* Safe cast: We tested < sconf->max_cache_object_size above */
/* Allocate and init mem_cache_object_t */
if (!mobj) {
return DECLINED;
}
/* Finish initing the cache object */
/* Safe cast: We tested < sconf->max_cache_object_size above */
/* Place the cache_object_t into the hash table.
* Note: Perhaps we should wait to put the object in the
* hash table when the object is complete? I add the object here to
* avoid multiple threads attempting to cache the same content only
* to discover at the very end that only one of them will succeed.
* Furthermore, adding the cache object to the table at the end could
* open up a subtle but easy to exploit DoS hole: someone could request
* a very large file with multiple requests. Better to detect this here
* rather than after the cache object has been completely built and
* initialized...
* XXX Need a way to insert into the cache w/o such coarse grained locking
*/
}
if (!tmp_obj) {
/* Add a refcount to account for the reference by the
* hashtable in the cache. Refcount should be 2 now, one
* for this thread, and one for the cache.
*/
}
}
if (tmp_obj) {
/* This thread collided with another thread loading the same object
* into the cache at the same time. Defer to the other thread which
* is further along.
*/
return DECLINED;
}
/* Populate the cache handle */
return OK;
}
{
}
{
}
{
/* Look up entity keyed to 'url' */
}
if (obj) {
/* cache is worried about overall counts, not 'open' ones */
/* If this is a subrequest, register the cleanup against
* the main request. This will prevent the cache object
* from being cleaned up from under the request after the
* subrequest is destroyed.
*/
rtmp = r;
while (rtmp) {
}
}
else {
}
}
}
if (!obj) {
return DECLINED;
}
/* Initialize the cache_handle */
return OK;
}
/* remove_entity()
* Notes:
* refcount should be at least 1 upon entry to this function to account
* for this thread's reference to the object. If the refcount is 1, then
* object has been removed from the cache by another thread and this thread
* is the last thread accessing the object.
*/
static int remove_entity(cache_handle_t *h)
{
}
/* If the entity is still in the cache, remove it and decrement the
* refcount. If the entity is not in the cache, do nothing. In both cases
* decrement_refcount called by the last thread referencing the object will
* trigger the cleanup.
*/
}
}
return OK;
}
{
apr_ssize_t i;
apr_size_t len = 0;
apr_size_t idx = 0;
char *buf;
if (*nelts == 0 ) {
return APR_SUCCESS;
}
return APR_ENOMEM;
}
}
/* Transfer the headers into a contiguous memory block */
if (!buf) {
return APR_ENOMEM;
}
for (i = 0; i < *nelts; ++i) {
}
return APR_SUCCESS;
}
int num_headers,
apr_table_t *t )
{
int i;
for (i = 0; i < num_headers; ++i) {
}
return APR_SUCCESS;
}
/* Define request processing hook handlers */
/* remove_url()
* Notes:
*/
static int remove_url(const char *key)
{
int cleanup = 0;
}
if (obj) {
/* For performance, cleanup cache object after releasing the lock */
}
}
if (cleanup) {
}
return OK;
}
{
int rc;
/* ### FIXME: These two items should not be saved. */
h->req_hdrs);
h->resp_hdrs);
h->resp_err_hdrs);
r->subprocess_env);
r->notes);
/* Content-Type: header may not be set if content is local since
* CACHE_IN runs before header filters....
*/
return rc;
}
{
apr_bucket *b;
/* CACHE_TYPE_FILE */
}
else {
/* CACHE_TYPE_HEAP */
}
return APR_SUCCESS;
}
{
int rc;
/*
* The cache needs to keep track of the following information:
* - Date, LastMod, Version, ReqTime, RespTime, ContentLength
* - The original request headers (for Vary)
* - The original response headers (for returning with a cached response)
* - The body of the message
*/
&mobj->num_req_hdrs,
r->headers_in);
if (rc != APR_SUCCESS) {
return rc;
}
/* Precompute how much storage we need to hold the headers */
r->server));
if (rc != APR_SUCCESS) {
return rc;
}
r->err_headers_out,
r->server));
if (rc != APR_SUCCESS) {
return rc;
}
r->subprocess_env );
if (rc != APR_SUCCESS) {
return rc;
}
if (rc != APR_SUCCESS) {
return rc;
}
/* Init the info struct */
}
}
if (info->response_time) {
}
if (info->request_time) {
}
}
if (info->content_type) {
return APR_ENOMEM;
}
}
return APR_ENOMEM;
}
}
return APR_ENOMEM;
}
}
return APR_ENOMEM;
}
}
return APR_SUCCESS;
}
{
apr_bucket *e;
char *cur;
int eos = 0;
int fd = 0;
int other = 0;
/* We can cache an open file descriptor if:
* - the brigade contains one and only one file_bucket &&
* - the brigade is complete &&
* - the file_bucket is the last data bucket in the brigade
*/
for (e = APR_BRIGADE_FIRST(b);
e != APR_BRIGADE_SENTINEL(b);
e = APR_BUCKET_NEXT(e))
{
if (APR_BUCKET_IS_EOS(e)) {
eos = 1;
}
else if (APR_BUCKET_IS_FILE(e)) {
apr_bucket_file *a = e->data;
fd++;
}
else {
other++;
}
}
const char *name;
/* Open a new XTHREAD handle to the file */
APR_OS_DEFAULT, r->pool);
if (rv != APR_SUCCESS) {
return rv;
}
/* Open for business */
return APR_SUCCESS;
}
/* Content not suitable for fd caching. Cache in-memory instead. */
}
/*
* FD cacheing is not enabled or the content was not
* suitable for fd caching.
*/
return APR_ENOMEM;
}
}
/* Iterate accross the brigade and populate the cache storage */
for (e = APR_BRIGADE_FIRST(b);
e != APR_BRIGADE_SENTINEL(b);
e = APR_BUCKET_NEXT(e))
{
const char *s;
if (APR_BUCKET_IS_EOS(e)) {
/* Caching a streamed response. Reallocate a buffer of the
* correct size and copy the streamed response into that
* buffer */
if (!buf) {
return APR_ENOMEM;
}
/* Now comes the crufty part... there is no way to tell the
* cache that the size of the object has changed. We need
* to remove the object, update the size and re-add the
* object, all under protection of the lock.
*/
}
/* Has the object been ejected from the cache?
*/
/* Object is still in the cache, remove it, update the len field then
* replace it under protection of sconf->lock.
*/
/* For illustration, cache no longer has reference to the object
* so decrement the refcount
* apr_atomic_dec32(&obj->refcount);
*/
/* For illustration, cache now has reference to the object, so
* increment the refcount
* apr_atomic_inc32(&obj->refcount);
*/
}
else if (tobj) {
/* Different object with the same key found in the cache. Doing nothing
* here will cause the object refcount to drop to 0 in decrement_refcount
* and the object will be cleaned up.
*/
} else {
/* Object has been ejected from the cache, add it back to the cache */
}
}
}
/* Open for business */
break;
}
if (rv != APR_SUCCESS) {
return rv;
}
if (len) {
/* Check for buffer overflow */
return APR_ENOMEM;
}
else {
}
}
/* This should not fail, but if it does, we are in BIG trouble
* cause we just stomped all over the heap.
*/
}
return APR_SUCCESS;
}
/**
* Configuration and start-up
*/
{
int threaded_mpm;
/* Sanity check the cache configuration */
"MCacheMaxObjectSize must be greater than MCacheMinObjectSize");
return DONE;
}
"MCacheSize must be greater than MCacheMaxObjectSize");
return DONE;
}
/* Issue a notice only if something other than the default config
* is being used */
"MCacheMaxStreamingBuffer must be less than or equal to MCacheMaxObjectSize. "
"Resetting MCacheMaxStreamingBuffer to MCacheMaxObjectSize.");
}
}
"MCacheMaxStreamingBuffer must be greater than or equal to MCacheMinObjectSize. "
"Resetting MCacheMaxStreamingBuffer to MCacheMinObjectSize.");
}
if (threaded_mpm) {
}
if (sconf->cache_cache)
return OK;
return -1;
}
static const char
{
return "MCacheSize argument must be an integer representing the max cache size in KBytes.";
}
return NULL;
}
static const char
{
return "MCacheMinObjectSize value must be an integer (bytes)";
}
return NULL;
}
static const char
{
return "MCacheMaxObjectSize value must be an integer (bytes)";
}
return NULL;
}
static const char
{
return "MCacheMaxObjectCount value must be an integer";
}
return NULL;
}
static const char
{
}
else {
}
else {
return "currently implemented algorithms are LRU and GDSF";
}
}
return NULL;
}
const char *arg)
{
char *err;
return "MCacheMaxStreamingBuffer value must be a number";
}
return NULL;
}
static const command_rec cache_cmds[] =
{
"The maximum amount of memory used by the cache in KBytes"),
"The maximum number of objects allowed to be placed in the cache"),
"The minimum size (in bytes) of an object to be placed in the cache"),
"The maximum size (in bytes) of an object to be placed in the cache"),
"The algorithm used to remove entries from the cache (default: GDSF)"),
"Maximum number of bytes of content to buffer for a streamed response"),
{NULL}
};
static const cache_provider cache_mem_provider =
{
};
static const cache_provider cache_fd_provider =
{
};
static void register_hooks(apr_pool_t *p)
{
/* cache initializer */
/* cache_hook_init(cache_mem_init, NULL, NULL, APR_HOOK_MIDDLE); */
/*
cache_hook_create_entity(create_entity, NULL, NULL, APR_HOOK_MIDDLE);
cache_hook_open_entity(open_entity, NULL, NULL, APR_HOOK_MIDDLE);
cache_hook_remove_url(remove_url, NULL, NULL, APR_HOOK_MIDDLE);
*/
}
{
NULL, /* create per-directory config structure */
NULL, /* merge per-directory config structures */
create_cache_config, /* create per-server config structure */
NULL, /* merge per-server config structures */
cache_cmds, /* command apr_table_t */
};