/* 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; }