mirror of
https://github.com/open-simh/simtools.git
synced 2026-01-22 10:31:47 +00:00
Too much to list all, but includes (in no particular order): - Cleanup for 64-bit builds, MSVC warnings. - Structured help - Help file compiler. - Supports volsets, writes/create work. - Support for I18n in messages, help. - Makefiles. - Initialize volume/volset - Command line editing/history Builds and works on Linux and Windows (VS). Not recently built or tested on other platforms, but not intentinonally broken.
452 lines
15 KiB
C
452 lines
15 KiB
C
/* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#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;
|
|
}
|