/* cache.c */
#include "copyright.h"
#include "config.h"
#include <stdio.h>
#include "teeny.h"
#include "db.h"
/*
* Cache management stuff. Also handles allocating chunks from the chunkfile
* and so on. Note: This DOES NOT mess with the disk, only with internal data
* structures. Basically the low level object management. Messes with
* descriptors to indicate whether things are or are not in cache.
*
* This also has routines for managing descriptors efficiently. malloc()ing them
* one at a time is death, 'cause we have absolutely *fuckloads* of them
* around.
*/
/*
* The cache is stored as a chain, in order by most recently used. Use
* touch() to indicate a usage.
*/
struct obj_data *main_cache = NULL;
long cache_usage = 0;/* Total cache usage at present */
long cache_size;
int cache_hits = 0;
int cache_errors = 0;
int cache_misses = 0;
void cache_trim();
/*
* If this is a 1, cache_trim() will NOT freeze things to disk. This can be
* used to protect the chunkfile from writes for a bit, if you are copyinf it
* around.
*/
int cache_locked = 0;
/*
* Initialize the cache to some size or other.
*/
void initialize_cache(size)
long size;
{
cache_size = size;
cache_usage = 0;
}
/*
* Deletes an object from cache.
*/
void cache_delete(obj)
struct obj_data *obj;
{
if (obj == obj->back) { /* Only thing here.. */
main_cache = NULL;
return;
}
if (main_cache == obj) {
main_cache = obj->fwd;
}
/* Unlink this sucker */
(obj->back)->fwd = obj->fwd;
(obj->fwd)->back = obj->back;
cache_usage -= DSC_SIZE(obj->descriptor);
#ifdef CACHEDEBUG
printf("cache_delete ");
cache_dump();
#endif /* CACHEDEBUG */
}
/*
* This moves an object to the head of the usage list, making it the most
* recently used. Call this whenever you reference an object.
*/
void touch(obj)
struct obj_data *obj;
{
if (obj == main_cache) { /* Already at the top */
return;
}
/* Unlink it */
(obj->back)->fwd = obj->fwd;
(obj->fwd)->back = obj->back;
/* Link it back in at the head. Remember, fwd is */
/* the next most recently used object. */
obj->fwd = main_cache;
obj->back = main_cache->back;
(main_cache->back)->fwd = obj;
main_cache->back = obj;
/* This is now the most recently used. */
main_cache = obj;
#ifdef CACHEDEBUG
printf("touch: ");
cache_dump();
#endif /* CACHEDEBUG */
}
/*
* This stuffs the object into cache, and then trims the cache down. All
* cache 'size' data goes by the size field on the descriptor, which is *on
* disk* size, not actual in memory size. Too bad.
*
* This and cache_flush() are the only things that should *ever* freeze an
* object to disk. Do it otherwise, and you better have a good reason.
*
* NOTE: The object better NOT be in cache when this is called.
*/
void cache_insert(obj)
struct obj_data *obj;
{
if (main_cache == NULL) { /* Empty cache */
main_cache = obj->back = obj->fwd = obj;
cache_usage = DSC_SIZE(obj->descriptor);
return;
}
/* Stuff it in at the head -- it's the most recently used. */
obj->fwd = main_cache;
obj->back = main_cache->back;
(main_cache->back)->fwd = obj;
main_cache->back = obj;
/* This is now the most recently used. */
main_cache = obj;
/* Now update cache usage, and trim the cache down to size */
cache_usage += DSC_SIZE(obj->descriptor);
cache_trim();
#ifdef CACHEDEBUG
printf("cache_insert");
cache_dump();
#endif /* CACHEDEBUG */
}
#ifdef CACHEDEBUG
void cache_dump()
{
struct obj_data *obj;
obj = main_cache;
if (obj == NULL) {
printf("cache is empty\n");
return;
}
do {
printf(" %s", DSC_NAME(obj->descriptor));
obj = obj->fwd;
} while (obj != main_cache);
printf("\n");
}
#endif /* CACHEDEBUG */
/*
* Locks the cache so stuff is guaranteed to remain resident.
*/
void lock_cache()
{
cache_locked = 1;
}
/*
* Unlocks the cache so things can get flushed to disk.
*/
void unlock_cache()
{
cache_locked = 0;
}
/*
* Trims the cache down to size, unless it's locked.
*/
void cache_trim()
{
struct obj_data *obj;
struct dsc *thedsc;
if (cache_locked)
return;
while (cache_usage > cache_size) {
/* Unlink the object back of the head */
obj = main_cache->back;
if (obj == main_cache) {
warning("cache_trim", "cache WAY too small!");
return;
}
(obj->back)->fwd = obj->fwd;
(obj->fwd)->back = obj->back;
thedsc = obj->descriptor;
cache_usage -= DSC_SIZE(thedsc);
/* Chill this object */
disk_freeze(obj->descriptor);
}
}
/*
* This flushes the entire cache to disk (for dump-like purposes).
*/
void cache_flush()
{
struct obj_data *obj, *next;
if (main_cache == NULL) {
return;
}
obj = main_cache;
do {
/* Be careful here. disk_freeze() blows away the obj */
next = obj->fwd;
disk_freeze(obj->descriptor);
obj = next;
} while (obj != main_cache);
main_cache = NULL;
cache_usage = 0;
cache_hits = 0;
cache_misses = 0;
}
/*
* Descriptor management code.
*/
struct dsc *free_descriptors = NULL;
/*
* Allocate a bunch of descriptors, and shove them on the free descriptor
* list.
*/
void alloc_dsc(n)
int n;
{
struct dsc *tmp1, *tmp2;
tmp1 = tmp2 = (struct dsc *) ty_malloc(n * sizeof(struct dsc), "alloc_dsc");
/* String them together into a list */
for (; n > 1; n--) {
(tmp2->ptr).next = &(tmp2[1]); /* Point at the next */
tmp2++;
}
/* Link this mess in */
(tmp2->ptr).next = free_descriptors;
free_descriptors = tmp1;
}
/*
* Grab a descriptor.
*/
struct dsc *
get_descriptor()
{
struct dsc *ret;
if (free_descriptors == NULL) {
alloc_dsc(32); /* Get a bunch more */
}
ret = free_descriptors;
free_descriptors = (ret->ptr).next; /* Might be NULL. OK. */
return (ret);
}
/*
* Give a descriptor back.
*/
void free_descriptor(dsc)
struct dsc *dsc;
{
(dsc->ptr).next = free_descriptors;
free_descriptors = dsc;
}