/* 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; int cache_usage = 0;/* Total cache usage at present */ long cache_size; /* * 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; /* * The free chunks are stored as a list of descriptors, biggest chunk first. * This leads to a fast worst-fit algorithm. Forward points toward less * recently used objects. */ struct dsc *free_chunks = NULL; long chunk_eof = 0L; /* Contains the offset of the end of the * chunkfile */ /* * 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 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. */ 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; } /* * Places a descriptor on the free chunk list. */ void free_chunk(dsc) struct dsc *dsc; { struct dsc *current, *last, *here; long start, end; if (free_chunks == NULL) { (dsc->ptr).next = NULL; free_chunks = dsc; return; } current = free_chunks; last = here = NULL; /* Charge down the list, seeing if this amalgamates with any */ /* existing free blocks. Track where to insert it, in case not */ start = DSC_CHUNK(dsc); end = start + DSC_SIZE(dsc); while (current != NULL) { /* If no amalgamation, insert it here? If the first */ /* chunk is smaller, or if all the chunks are bigger */ /* here will be NULL. Be careful at the end, therefore */ if (here == NULL && DSC_SIZE(current) < DSC_SIZE(dsc)) { here = last; } /* Now see if it amalgamates. */ if (start == (DSC_CHUNK(current) + DSC_SIZE(current))) { DSC_SIZE(current) += DSC_SIZE(dsc); free_descriptor(dsc); del_free_chunk(current, last); free_chunk(current); return; } else if (end == DSC_CHUNK(current)) { DSC_SIZE(dsc) += DSC_SIZE(current); del_free_chunk(current, last); free_descriptor(current); free_chunk(dsc); return; } last = current; current = (current->ptr).next; } /* Post mortem. Only get here we didn't amalgamate */ if (here == NULL) { /* Either it gos in at the head, or at the tail. */ if (DSC_SIZE(last) > DSC_SIZE(dsc)) { /* Tail */ (dsc->ptr).next = NULL; (last->ptr).next = dsc; } else { /* Head */ (dsc->ptr).next = free_chunks; free_chunks = dsc; } } else { /* Insert it immediately after here */ (dsc->ptr).next = (here->ptr).next; (here->ptr).next = dsc; } } /* * This allocates a chunk from the free list of chunks, if it can, otherwise * it just specifies the and of the chunkfile. It returns an offset to write * at, in either case. In particular it DOES NOT return a descriptor. */ long get_chunk(n) int n; { long ret; struct dsc *tmp; if (free_chunks == NULL) { /* No free chunks. */ #ifdef CACHEDEBUG printf("No free chunks. Allocating from EOF\n"); #endif /* CACHEDEBUG */ ret = chunk_eof; chunk_eof += n; return (ret); } /* See if the first (biggest) chunk is big enough. */ tmp = free_chunks; if (DSC_SIZE(tmp) > n) { /* Return the offset of the start */ #ifdef CACHEDEBUG printf("Biggest chunk was big enough. Pruning it."); #endif /* CACHEDEBUG */ ret = DSC_CHUNK(tmp); /* * Adjust the size & offset, unlink it, and re-insert it in order */ DSC_SIZE(tmp) -= n; DSC_CHUNK(tmp) += n; free_chunks = (tmp->ptr).next; free_chunk(tmp); return (ret); } else if (DSC_SIZE(tmp) == n) { /* Exact match! */ /* Nuke this descriptor completely */ #ifdef CACHEDEBUG printf("Biggest chunk was exact match, allocating it\n"); #endif /* CACHEDEBUG */ ret = DSC_CHUNK(tmp); free_chunks = (tmp->ptr).next; free_descriptor(tmp); return (ret); } /* If we can't find a big enough chunk, do this */ #ifdef CACHEDEBUG printf("No big enough chunks. Allocating from EOF.\n"); #endif /* CACHEDEBUG */ ret = chunk_eof; chunk_eof += n; return (ret); } /* * Deletes a descriptor from the list of free chunk descriptors. */ void del_free_chunk(dsc, last) struct dsc *dsc; struct dsc *last; { if (free_chunks == dsc) { free_chunks = (dsc->ptr).next; return; } /* Last had better only be NULL when dsc == free_chunks */ (last->ptr).next = (dsc->ptr).next; } /* * 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; }