dbm/
misc/
old-docs/
/* 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;
}