/* @@@HEAD@@@ // Object cache routines. // // This code is based on code written by Marcus J. Ranum. That code, and // therefore this derivative work, are Copyright (C) 1991, Marcus J. Ranum, // all rights reserved. */ #include <stdio.h> #include "config.h" #include "defs.h" #include "cache.h" #include "object.h" #include "memory.h" #include "db.h" #include "lookup.h" #include "log.h" #include "util.h" #include "ident.h" #define DEBUG_CACHE 0 /* // Store dummy objects for chain heads and tails. This is a little storage- // intensive, but it simplifies and speeds up the list operations. */ object_t * active; object_t * inactive; int load_count; /* used by cache_cleanup() and cache_retrieve() */ #if DEBUG_CACHE int _acounter = 0; int _icounter = 0; #endif /* // ---------------------------------------------------------------------- // // Requires: Shouldn't be called twice. // Modifies: active, inactive. // Effects: Builds an array of object chains in inactive, and an array of // empty object chains in active. // */ void init_cache(void) { object_t *obj; int i, j; active = EMALLOC(object_t, CACHE_WIDTH); inactive = EMALLOC(object_t, CACHE_WIDTH); load_count = 0; for (i = 0; i < CACHE_WIDTH; i++) { /* Active list starts out empty. */ active[i].next = active[i].prev = &active[i]; /* Inactive list begins as a chain of empty objects. */ inactive[i].next = inactive[i].prev = &inactive[i]; for (j = 0; j < CACHE_DEPTH; j++) { obj = EMALLOC(object_t, 1); obj->dbref = INV_OBJNUM; obj->ucounter=0; obj->prev = &inactive[i]; obj->next = inactive[i].next; obj->prev->next = obj->next->prev = obj; } } } /* // ---------------------------------------------------------------------- // // Requires: Initialized cache. // Modifies: Contents of active, inactive, database files // Effects: Returns an object holder linked to the head of the appropriate // active chain. Gets the object holder from the tail of the inactive // chain, swapping out the object there if necessary. If the inactive // inactive chain is empty, then we create a new holder. // */ object_t *cache_get_holder(long dbref) { int ind = dbref % CACHE_WIDTH; object_t *obj; if (inactive[ind].next != &inactive[ind]) { /* Use the object at the tail of the inactive list. */ obj = inactive[ind].prev; /* Check if we need to swap anything out. */ if (obj->dbref != INV_OBJNUM) { if (obj->dirty) { if (!db_put(obj, obj->dbref)) panic("Could not store an object."); } object_free(obj); } /* Unlink it from the inactive list. */ obj->prev->next = obj->next; obj->next->prev = obj->prev; } else { /* Allocate a new object. */ obj = EMALLOC(object_t, 1); } /* Link the object a the head of the active chain. */ obj->prev = &active[ind]; obj->next = active[ind].next; obj->prev->next = obj->next->prev = obj; obj->dirty = 0; obj->dead = 0; obj->refs = 1; obj->ucounter+=10; #if DEBUG_CACHE _acounter++; #endif obj->dbref = dbref; return obj; } /* // ---------------------------------------------------------------------- // // Requires: Initialized cache. // Modifies: Contents of active, inactive, database files // Effects: Returns the object associated with dbref, getting it from the cache // or from disk. If the object is in the inactive chain or is on // disk, it will be linked into the active chain. Returns NULL if no // object exists with the given dbref. // */ object_t *cache_retrieve(long dbref) { int ind = dbref % CACHE_WIDTH; object_t *obj; if (load_count > FORCED_CLEANUP_LIMIT) { #if DEBUG_CACHE printf("## Cache flood detected..."); #endif cache_cleanup(); } if (dbref < 0) return NULL; /* Search active chain for object. */ for (obj = active[ind].next; obj != &active[ind]; obj = obj->next) { if (obj->dbref == dbref) { obj->refs++; obj->ucounter+=10; return obj; } } /* Search inactive chain for object. */ for (obj = inactive[ind].next; obj != &inactive[ind]; obj = obj->next) { if (obj->dbref == dbref) { /* Remove object from inactive chain. */ obj->next->prev = obj->prev; obj->prev->next = obj->next; /* Install object at head of active chain. */ #if DEBUG_CACHE _icounter--; #endif obj->prev = &active[ind]; obj->next = active[ind].next; obj->prev->next = obj->next->prev = obj; obj->refs = 1; obj->ucounter+=10; #if DEBUG_CACHE _acounter++; #endif return obj; } } /* Cache miss. Find an object to load in from disk. */ obj = cache_get_holder(dbref); /* Read the object into the place-holder, if it's on disk. */ load_count++; if (db_get(obj, dbref)) { return obj; } else { /* Oops. Install holder at tail of inactive chain and return NULL. */ obj->dbref = INV_OBJNUM; obj->prev->next = obj->next; obj->next->prev = obj->prev; #if 1 obj->prev = inactive[ind].prev; obj->next = &inactive[ind]; obj->prev->next = obj->next->prev = obj; #else efree(obj); #endif return NULL; } } /* // ---------------------------------------------------------------------- */ object_t *cache_grab(object_t *obj) { obj->refs++; obj->ucounter+=10; return obj; } /* // ---------------------------------------------------------------------- // // Requires: Initialized cache. obj should point to an active object. // Modifies: obj, contents of active and inactive, database files. // Effects: Decreases the refcount on obj, unlinking it from the active chain // if the refcount hits zero. If the object is marked dead, then it // is destroyed when it is unlinked from the active chain. // */ void cache_discard(object_t *obj) { int ind; if (!obj) return; /* Decrease reference count. */ obj->refs--; if (obj->refs) return; #if DEBUG_CACHE _acounter--; #endif ind = obj->dbref % CACHE_WIDTH; /* Reference count hit 0; remove from active chain. */ obj->prev->next = obj->next; obj->next->prev = obj->prev; if (obj->dead) { /* The object is dead; remove it from the database, and install it at * the tail of the inactive chain. Be careful about this, since * object_destroy() can fiddle with the cache. We're safe as long as * obj isn't in any chains at the time of db_del(). */ db_del(obj->dbref); object_destroy(obj); obj->dbref = INV_OBJNUM; obj->prev = inactive[ind].prev; obj->next = &inactive[ind]; obj->prev->next = obj->next->prev = obj; } else { /* Install at head of inactive chain. */ obj->prev = &inactive[ind]; obj->next = inactive[ind].next; obj->prev->next = obj->next->prev = obj; #if DEBUG_CACHE _icounter++; #endif } } /* // ---------------------------------------------------------------------- // // Requires: Initialized cache. // Effects: Returns nonzero if an object exists with the given dbref. // */ int cache_check(long dbref) { int ind = dbref % CACHE_WIDTH; object_t *obj; if (dbref < 0) return 0; /* Search active chain. */ for (obj = active[ind].next; obj != &active[ind]; obj = obj->next) { if (obj->dbref == dbref) return 1; } /* Search inactive chain. */ for (obj = inactive[ind].next; obj != &inactive[ind]; obj = obj->next) { if (obj->dbref == dbref) return 1; } /* Check database on disk. */ return db_check(dbref); } /* // ---------------------------------------------------------------------- // // Requires: Initialized cache. // Modifies: Database files. // Effects: Writes out all objects in the cache which are marked dirty. // */ void cache_sync(void) { int i; object_t *obj; /* Traverse all the active and inactive chains. */ for (i = 0; i < CACHE_WIDTH; i++) { /* Check active chain. */ for (obj = active[i].next; obj != &active[i]; obj = obj->next) { if (obj->dirty) { if (!db_put(obj, obj->dbref)) panic("Could not store an object."); obj->dirty = 0; } } /* Check inactive chain. */ for (obj = inactive[i].next; obj != &inactive[i]; obj = obj->next) { if (obj->dbref != INV_OBJNUM && obj->dirty) { if (!db_put(obj, obj->dbref)) panic("Could not store an object."); obj->dirty = 0; } } } db_flush(); } /* // ---------------------------------------------------------------------- */ object_t *cache_first(void) { long dbref; cache_sync(); dbref = lookup_first_dbref(); if (dbref == INV_OBJNUM) return NULL; return cache_retrieve(dbref); } /* // ---------------------------------------------------------------------- */ object_t *cache_next(void) { long dbref; dbref = lookup_next_dbref(); if (dbref == INV_OBJNUM) return NULL; return cache_retrieve(dbref); } /* // ---------------------------------------------------------------------- // // Called during main loop to verify that no objects are active. // // JBB: Well, actually, its not called. Should really be re-written to check // the suspended task list to see what is not really active and what is dirty // */ void cache_sanity_check(void) { int i; for (i = 0; i < CACHE_WIDTH; i++) { if (active[i].next != &active[i]) panic("Active objects at start of main loop."); } } /* // ---------------------------------------------------------------------- // // Called during main loop to clean inactive objects from the cache // */ void cache_cleanup(void) { object_t * obj; int i, flood_bound = (load_count > FORCED_CLEANUP_LIMIT ? FORCED_CLEANUP_BOUND : 0); load_count = 0; for (i = 0; i < CACHE_WIDTH; i++) { for (obj = inactive[i].next; obj != &inactive[i]; obj = obj->next) { obj->ucounter >>= 1; if(obj->ucounter > flood_bound) { #if DISABLED if(obj->dbref == INV_OBJNUM) continue; /* Attempt to pack fragmented object storage space by reallocating it */ dbref = obj->dbref; if (obj->dirty && !db_put(obj, obj->dbref)) panic("Could not store an object."); object_free(obj); if(!db_get(obj, dbref)) obj->dbref = INV_OBJNUM; #endif continue; } #if DISABLED if (obj->dbref != INV_OBJNUM && obj->dirty) { #else if (obj->dbref != INV_OBJNUM) { #endif if (!db_put(obj, obj->dbref)) panic("Could not store an object."); obj->dirty = 0; } if(obj->dbref != INV_OBJNUM) { #if DEBUG_CACHE _icounter--; fprintf(stderr,"<%d\n",_icounter); #endif object_free(obj); obj->dbref = INV_OBJNUM; continue; } } } }