/* Cache.c Caching control routines */ /* * This is part of ODS2 written by Paul Nankervis, * email address: Paulnank@au1.ibm.com * * ODS2 is distributed freely for all members of the * VMS community to use. However all derived works * must maintain comments in their source to acknowledge * the contributions of the original author and * subsequent contributors. This is free software; no * warranty is offered, and while we believe it to be useful, * you use it at your own risk. */ /* * The theory is that all cachable objects share a common cache pool. * Any object with a reference count of zero is a candidate for * destruction. All cacheable objects have a 'struct CACHE' as the * first item of the object so that cache pointers and object pointers * are 'interchangeable'. All cache objects are also part of a binary * tree so that they can be located. There are many instances of these * binary trees: for files on a volume, file windows, file chunks * (segments) etc. Also each object can have an object manager: a * routine to handle special object deletion requirements (for example * to remove all file chunks before removing a file), or to flush * objects (write modified chunks to disk). * * The main routines is cache_find() which is used to search for and * place objects in a binary tree which is located by a tree pointer. * cache_touch() and cache_untouch() are used to bump and decrement * reference counts. Any object with a reference count of zero is a * candidate for destruction - but if it requires special action * before it is destroyed, such as deleting a subtree, flushing data * to disk, etc, then it should have an 'object manager' function * assigned which will be called at deletion time to take care of these * needs. * * This version of the cache routines attempts to maintain binary tree * balance by dynamically changing tree shape during search functions. * All objects remain in the binary tree until destruction so that they * can be re-used at any time. Objects with a zero reference count are * special in that they are kept in a 'least recently used' linked list. * When the reference count is decemented a flag is used to indicate * whether the object is likely to be referenced again so that the object * can be put in the 'correct' end of this list. * * Note: These routines are 'general' in that do not know anything * about ODS2 objects or structures.... */ #if !defined( DEBUG ) && defined( DEBUG_CACHE ) #define DEBUG DEBUG_CACHE #else #ifndef DEBUG #define DEBUG 0 #endif #endif /* *** TEMP TODO ***/ #undef DEBUG #define DEBUG 1 #include #include #include "cache.h" #include "ods2.h" #include "ssdef.h" #define IMBALANCE 5 /* Tree imbalance limit */ #define CACHELIM 256 /* Free object limit */ #define CACHEGOAL 128 /* Free object goal */ static int cachefinds = 0; static int cachecreated = 0; static int cachepurges = 0; static int cachepeak = 0; static int cachecount = 0; static int cachefreecount = 0; static int cachedeletes = 0; static int cachedeleteing = FALSE; /* Cache deletion in progress... */ struct CACHE lrulist = {&lrulist, &lrulist, NULL, NULL, NULL, NULL, 0, 0, 1, 0}; static void cache_deleter( struct CACHE *cacheobj, struct CACHE **actualobj ); /*************************************************************** cache_show() */ /* cache_show() - to print cache statistics */ void cache_show(void) { printf("CACHE_SHOW Find %d Create %d Purge %d Peak %d Count %d Free %d\n", cachefinds, cachecreated, cachepurges, cachepeak, cachecount, cachefreecount); if (cachecreated - cachedeletes != cachecount) { printf(" - Deleted %d\n", cachedeletes); } } /*********************************************************** cache_refcount() */ /* cache_refcount() - compute reference count for cache subtree... */ int cache_refcount( struct CACHE *cacheobj ) { register int refcount = 0; if( cacheobj != NULL ) { refcount = cacheobj->refcount; if (cacheobj->left != NULL) { refcount += cache_refcount(cacheobj->left); } if (cacheobj->right != NULL) { refcount += cache_refcount(cacheobj->right); } } return refcount; } /************************************************************* cache_delete() */ /* cache_delete() - blow away item from cache - allow item to select a proxy (!) and adjust 'the tree' containing the item. */ void cache_delete( struct CACHE *cacheobj ) { cache_deleter( cacheobj, NULL ); } /* Internal routine that does the work. Optionally returns the address of the * object actually deleted, which allows pointers to be adjusted correctly. */ static void cache_deleter( struct CACHE *cacheobj, struct CACHE **actualobj ) { while (cacheobj->objmanager != NULL) { register struct CACHE *proxyobj; cachedeleteing = TRUE; proxyobj = (*cacheobj->objmanager) (cacheobj, FALSE); cachedeleteing = FALSE; if( proxyobj == NULL ) { if( actualobj != NULL ) *actualobj = NULL; return; } if (proxyobj == cacheobj) break; cacheobj = proxyobj; } #if DEBUG if (cachedeleteing) printf("CACHE deletion while delete in progress\n"); #endif if (cacheobj->refcount != 0) { #if DEBUG printf("CACHE attempt to delete referenced object %d:%d\n", cacheobj->objtype, cacheobj->hashval); #endif abort(); if( actualobj != NULL ) *actualobj = NULL; return; } cacheobj->lastlru->nextlru = cacheobj->nextlru; cacheobj->nextlru->lastlru = cacheobj->lastlru; if (cacheobj->left == NULL) { if (cacheobj->right == NULL) { *(cacheobj->parent) = NULL; } else { cacheobj->right->parent = cacheobj->parent; *(cacheobj->parent) = cacheobj->right; } } else { if (cacheobj->right == NULL) { cacheobj->left->parent = cacheobj->parent; *(cacheobj->parent) = cacheobj->left; } else { register struct CACHE *path = cacheobj->right; if (path->left != NULL) { do { path = path->left; } while (path->left != NULL); *(path->parent) = path->right; if (path->right != NULL) path->right->parent = path->parent; path->right = cacheobj->right; path->right->parent = &path->right; } path->left = cacheobj->left; path->left->parent = &path->left; path->parent = cacheobj->parent; *(cacheobj->parent) = path; path->balance = 0; } } cachecount--; cachefreecount--; cachedeletes++; #if DEBUG cacheobj->nextlru = NULL; cacheobj->lastlru = NULL; cacheobj->left = NULL; cacheobj->right = NULL; cacheobj->parent = NULL; cacheobj->objmanager = NULL; cacheobj->hashval = 0; cacheobj->balance = 0; cacheobj->refcount = 0; #endif if( actualobj != NULL ) /* The returned value is used as a flag. It will not be dereferenced. */ *actualobj = cacheobj; free(cacheobj); /* This may be the supplied object, or it may have been replaced by a proxy. */ return; } /************************************************************** cache_purge() */ /* cache_purge() - trim size of free list */ void cache_purge( int all ) { if (!cachedeleteing) { register struct CACHE *cacheobj; cacheobj = lrulist.lastlru; cachepurges++; while( (all || cachefreecount > CACHEGOAL) && cacheobj != &lrulist ) { register struct CACHE *lastobj; lastobj = cacheobj->lastlru; #if DEBUG if (cacheobj->lastlru->nextlru != cacheobj || cacheobj->nextlru->lastlru != cacheobj || *(cacheobj->parent) != cacheobj) { printf("CACHE pointers in bad shape!\n"); } #endif if (cacheobj->refcount == 0) { struct CACHE *actualobj; cache_deleter( cacheobj, &actualobj ); if (actualobj != lastobj) cacheobj = lastobj; } else { cacheobj = lastobj; } } } } /************************************************************** cache_flush() */ /* cache_flush() - flush modified entries in cache */ void cache_flush(void) { register struct CACHE *cacheobj; for( cacheobj = lrulist.lastlru; cacheobj != &lrulist; cacheobj = cacheobj->lastlru ) { if (cacheobj->objmanager != NULL) { (*cacheobj->objmanager) (cacheobj, TRUE); } } } /************************************************************* cache_remove() */ /* cache_remove() - delete all possible objects from cache subtree */ void cache_remove( struct CACHE *cacheobj ) { if (cacheobj != NULL) { if (cacheobj->left != NULL) cache_remove(cacheobj->left); if (cacheobj->right != NULL) cache_remove(cacheobj->right); if (cacheobj->refcount == 0) { struct CACHE *delobj; do { cache_deleter(cacheobj, &delobj); } while (delobj != NULL && delobj != cacheobj); } } } /************************************************************** cache_touch() */ /* cache_touch() - to increase the access count on an object... */ void cache_touch(struct CACHE *cacheobj) { if (cacheobj->refcount++ == 0) { #if DEBUG if (cacheobj->nextlru == NULL || cacheobj->lastlru == NULL) { printf("CACHE LRU pointers corrupt!\n"); abort(); } #endif cacheobj->nextlru->lastlru = cacheobj->lastlru; cacheobj->lastlru->nextlru = cacheobj->nextlru; cacheobj->nextlru = NULL; cacheobj->lastlru = NULL; cachefreecount--; } } /************************************************************ cache_untouch() */ /* Deaccess an object. * Recycle => TRUE puts object at front of LRU list, indicating that * it's likely to be reused soon. Note that untouch() can * trigger a purge, so the object can be deleted on return. * This won't happen if recycle is TRUE because purge will maintain * CACHEGOAL LRU entries, which must be non-zero. */ void cache_untouch( struct CACHE *cacheobj, int recycle ) { if( cacheobj->refcount > 0 ) { if( --cacheobj->refcount == 0 ) { #if DEBUG if( cacheobj->nextlru != NULL || cacheobj->lastlru != NULL ) { printf("CACHE LRU pointers corrupt\n"); } #endif if( recycle ) { cacheobj->nextlru = lrulist.nextlru; cacheobj->lastlru = &lrulist; cacheobj->nextlru->lastlru = cacheobj; lrulist.nextlru = cacheobj; } else { cacheobj->lastlru = lrulist.lastlru; cacheobj->nextlru = &lrulist; cacheobj->lastlru->nextlru = cacheobj; lrulist.lastlru = cacheobj; } if( ++cachefreecount >= CACHELIM ) cache_purge(FALSE); } } else { #if DEBUG printf("CACHE untouch limit exceeded\n"); #endif abort(); } } /*************************************************************** cache_find() */ /* cache_find() - to find or create cache entries... * * The grand plan here was to use a hash code as a quick key * and call a compare function for duplicates. So far no data * type actually works like this - they either have a unique binary * key, or all records rely on the compare function - sigh! * Never mind, the potential is there! :-) * * This version will call a creation function to allocate and * initialize an object if it is not found. */ void *cache_find( void **root, uint32_t hashval, void *keyval, vmscond_t *retsts, int (*compare_func) ( uint32_t hashval, void *keyval, void *node ), void *(*create_func) ( uint32_t hashval, void *keyval, vmscond_t *retsts ) ) { register struct CACHE *cacheobj, **parent; parent = (struct CACHE **) root; cachefinds++; while( (cacheobj = *parent) != NULL ) { register int cmp; cmp = hashval - cacheobj->hashval; #if DEBUG if (cacheobj->parent != parent) { printf("CACHE Parent pointer is corrupt\n"); } #endif if (cmp == 0 && compare_func != NULL) { cmp = (*compare_func) (hashval, keyval, cacheobj); } if (cmp == 0) { cache_touch(cacheobj); if (retsts != NULL) *retsts = SS$_NORMAL; return cacheobj; } if (cmp < 0) { #ifdef IMBALANCE register struct CACHE *left_path; left_path = cacheobj->left; if (left_path != NULL && cacheobj->balance-- < -IMBALANCE) { cacheobj->left = left_path->right; if (cacheobj->left != NULL) { cacheobj->left->parent = &cacheobj->left; } left_path->right = cacheobj; cacheobj->parent = &left_path->right; *parent = left_path; left_path->parent = parent; cacheobj->balance = 0; } else { parent = &cacheobj->left; } } else { register struct CACHE *right_path; right_path = cacheobj->right; if (right_path != NULL && cacheobj->balance++ > IMBALANCE) { cacheobj->right = right_path->left; if (cacheobj->right != NULL) { cacheobj->right->parent = &cacheobj->right; } right_path->left = cacheobj; cacheobj->parent = &right_path->left; *parent = right_path; right_path->parent = parent; cacheobj->balance = 0; } else { parent = &cacheobj->right; } #else parent = &cacheobj->left; } else { parent = &cacheobj->right; #endif } } if (create_func == NULL) { if (retsts != NULL) *retsts = SS$_ITEMNOTFOUND; } else { cacheobj = (*create_func) (hashval, keyval, retsts); if (cacheobj != NULL) { cacheobj->nextlru = NULL; cacheobj->lastlru = NULL; cacheobj->left = NULL; cacheobj->right = NULL; cacheobj->parent = parent; cacheobj->hashval = hashval; cacheobj->refcount = 1; cacheobj->balance = 0; *parent = cacheobj; cachecreated++; if (cachecount++ >= cachepeak) cachepeak = cachecount; } } return cacheobj; }