
/*
 * $Id: store_repl_heap.c,v 1.11 2005/05/17 16:56:44 hno Exp $
 *
 * DEBUG: section ?     HEAP based removal policies
 * AUTHOR: Henrik Nordstrom
 *
 * Based on the ideas of the heap policy implemented by John Dilley of
 * Hewlett Packard. Rewritten from scratch when modularizing the removal
 * policy implementation of Squid.
 *
 * For details on the original heap policy work and the thinking behind see
 * http://www.hpl.hp.com/techreports/1999/HPL-1999-69.html
 *
 * 
 * SQUID Web Proxy Cache          http://www.squid-cache.org/
 * ----------------------------------------------------------
 *
 *  Squid is the result of efforts by numerous individuals from
 *  the Internet community; see the CONTRIBUTORS file for full
 *  details.   Many organizations have provided support for Squid's
 *  development; see the SPONSORS file for full details.  Squid is
 *  Copyrighted (C) 2001 by the Regents of the University of
 *  California; see the COPYRIGHT file for full details.  Squid
 *  incorporates software developed and/or copyrighted by other
 *  sources; see the CREDITS file for full details.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
 *
 */

#include "squid.h"
#include "heap.h"
#include "store_heap_replacement.h"

REMOVALPOLICYCREATE createRemovalPolicy_heap;

static int nr_heap_policies = 0;

typedef struct _HeapPolicyData HeapPolicyData;
struct _HeapPolicyData {
    RemovalPolicy *policy;
    heap *heap;
    heap_key_func *keyfunc;
    int count;
    int nwalkers;
    enum heap_entry_type {
	TYPE_UNKNOWN = 0, TYPE_STORE_ENTRY, TYPE_STORE_MEM
    } type;
};

/* Hack to avoid having to remember the RemovalPolicyNode location.
 * Needed by the purge walker.
 */
static enum heap_entry_type
heap_guessType(StoreEntry * entry, RemovalPolicyNode * node)
{
    if (node == &entry->repl)
	return TYPE_STORE_ENTRY;
    if (entry->mem_obj && node == &entry->mem_obj->repl)
	return TYPE_STORE_MEM;
    fatal("Heap Replacement: Unknown StoreEntry node type");
    return TYPE_UNKNOWN;
}
#define SET_POLICY_NODE(entry,value) \
    switch(heap->type) { \
    case TYPE_STORE_ENTRY: entry->repl.data = value; break ; \
    case TYPE_STORE_MEM: entry->mem_obj->repl.data = value ; break ; \
    default: break; \
    }

static void
heap_add(RemovalPolicy * policy, StoreEntry * entry, RemovalPolicyNode * node)
{
    HeapPolicyData *heap = policy->_data;
    assert(!node->data);
    if (EBIT_TEST(entry->flags, ENTRY_SPECIAL))
	return;			/* We won't manage these.. they messes things up */
    node->data = heap_insert(heap->heap, entry);
    heap->count += 1;
    if (!heap->type)
	heap->type = heap_guessType(entry, node);
    /* Add a little more variance to the aging factor */
    heap->heap->age += heap->heap->age / 100000000;
}

static void
heap_remove(RemovalPolicy * policy, StoreEntry * entry,
    RemovalPolicyNode * node)
{
    HeapPolicyData *heap = policy->_data;
    heap_node *hnode = node->data;
    if (!hnode)
	return;
    heap_delete(heap->heap, hnode);
    node->data = NULL;
    heap->count -= 1;
}

static void
heap_referenced(RemovalPolicy * policy, const StoreEntry * entry,
    RemovalPolicyNode * node)
{
    HeapPolicyData *heap = policy->_data;
    heap_node *hnode = node->data;
    if (!hnode)
	return;
    heap_update(heap->heap, hnode, (StoreEntry *) entry);
}

/** RemovalPolicyWalker **/

typedef struct _HeapWalkData HeapWalkData;
struct _HeapWalkData {
    int current;
};

static const StoreEntry *
heap_walkNext(RemovalPolicyWalker * walker)
{
    HeapWalkData *heap_walk = walker->_data;
    RemovalPolicy *policy = walker->_policy;
    HeapPolicyData *heap = policy->_data;
    StoreEntry *entry;
    if (heap_walk->current >= heap_nodes(heap->heap))
	return NULL;		/* done */
    entry = (StoreEntry *) heap_peep(heap->heap, heap_walk->current++);
    return entry;
}

static void
heap_walkDone(RemovalPolicyWalker * walker)
{
    RemovalPolicy *policy = walker->_policy;
    HeapPolicyData *heap = policy->_data;
    assert(strcmp(policy->_type, "heap") == 0);
    assert(heap->nwalkers > 0);
    heap->nwalkers -= 1;
    safe_free(walker->_data);
    cbdataFree(walker);
}

static RemovalPolicyWalker *
heap_walkInit(RemovalPolicy * policy)
{
    HeapPolicyData *heap = policy->_data;
    RemovalPolicyWalker *walker;
    HeapWalkData *heap_walk;
    heap->nwalkers += 1;
    walker = cbdataAlloc(RemovalPolicyWalker);
    heap_walk = xcalloc(1, sizeof(*heap_walk));
    heap_walk->current = 0;
    walker->_policy = policy;
    walker->_data = heap_walk;
    walker->Next = heap_walkNext;
    walker->Done = heap_walkDone;
    return walker;
}

/** RemovalPurgeWalker **/

typedef struct _HeapPurgeData HeapPurgeData;
struct _HeapPurgeData {
    link_list *locked_entries;
    heap_key min_age;
};

static StoreEntry *
heap_purgeNext(RemovalPurgeWalker * walker)
{
    HeapPurgeData *heap_walker = walker->_data;
    RemovalPolicy *policy = walker->_policy;
    HeapPolicyData *heap = policy->_data;
    StoreEntry *entry;
    heap_key age;
  try_again:
    if (!heap_nodes(heap->heap) > 0)
	return NULL;		/* done */
    age = heap_peepminkey(heap->heap);
    entry = heap_extractmin(heap->heap);
    if (storeEntryLocked(entry)) {
	storeLockObject(entry);
	linklistPush(&heap_walker->locked_entries, entry);
	goto try_again;
    }
    heap_walker->min_age = age;
    SET_POLICY_NODE(entry, NULL);
    return entry;
}

static void
heap_purgeDone(RemovalPurgeWalker * walker)
{
    HeapPurgeData *heap_walker = walker->_data;
    RemovalPolicy *policy = walker->_policy;
    HeapPolicyData *heap = policy->_data;
    StoreEntry *entry;
    assert(strcmp(policy->_type, "heap") == 0);
    assert(heap->nwalkers > 0);
    heap->nwalkers -= 1;
    if (heap_walker->min_age > 0) {
	heap->heap->age = heap_walker->min_age;
	debug(81, 3) ("heap_purgeDone: Heap age set to %f\n",
	    (double) heap->heap->age);
    }
    /*
     * Reinsert the locked entries
     */
    while ((entry = linklistShift(&heap_walker->locked_entries))) {
	heap_node *node = heap_insert(heap->heap, entry);
	SET_POLICY_NODE(entry, node);
	storeUnlockObject(entry);
    }
    safe_free(walker->_data);
    cbdataFree(walker);
}

static RemovalPurgeWalker *
heap_purgeInit(RemovalPolicy * policy, int max_scan)
{
    HeapPolicyData *heap = policy->_data;
    RemovalPurgeWalker *walker;
    HeapPurgeData *heap_walk;
    heap->nwalkers += 1;
    walker = cbdataAlloc(RemovalPurgeWalker);
    heap_walk = xcalloc(1, sizeof(*heap_walk));
    heap_walk->min_age = 0.0;
    heap_walk->locked_entries = NULL;
    walker->_policy = policy;
    walker->_data = heap_walk;
    walker->max_scan = max_scan;
    walker->Next = heap_purgeNext;
    walker->Done = heap_purgeDone;
    return walker;
}

static void
heap_free(RemovalPolicy * policy)
{
    HeapPolicyData *heap = policy->_data;
    /* Make some verification of the policy state */
    assert(strcmp(policy->_type, "heap") == 0);
    assert(heap->nwalkers);
    assert(heap->count);
    /* Ok, time to destroy this policy */
    safe_free(policy->_data);
    memset(policy, 0, sizeof(*policy));
    cbdataFree(policy);
}

RemovalPolicy *
createRemovalPolicy_heap(wordlist * args)
{
    RemovalPolicy *policy;
    HeapPolicyData *heap_data;
    const char *keytype;
    /* Allocate the needed structures */
    policy = cbdataAlloc(RemovalPolicy);
    heap_data = xcalloc(1, sizeof(*heap_data));
    /* Initialize the policy data */
    heap_data->policy = policy;
    if (args) {
	keytype = args->key;
	args = args->next;
    } else {
	debug(81, 1) ("createRemovalPolicy_heap: No key type specified. Using LRU\n");
	keytype = "LRU";
    }
    if (!strcmp(keytype, "GDSF"))
	heap_data->keyfunc = HeapKeyGen_StoreEntry_GDSF;
    else if (!strcmp(keytype, "LFUDA"))
	heap_data->keyfunc = HeapKeyGen_StoreEntry_LFUDA;
    else if (!strcmp(keytype, "LRU"))
	heap_data->keyfunc = HeapKeyGen_StoreEntry_LRU;
    else {
	debug(81, 0) ("createRemovalPolicy_heap: Unknown key type \"%s\". Using LRU\n",
	    keytype);
	heap_data->keyfunc = HeapKeyGen_StoreEntry_LRU;
    }
    /* No additional arguments expected */
    assert(!args);
    heap_data->heap = new_heap(1000, heap_data->keyfunc);
    heap_data->heap->age = 1.0;
    /* Populate the policy structure */
    policy->_type = "heap";
    policy->_data = heap_data;
    policy->Free = heap_free;
    policy->Add = heap_add;
    policy->Remove = heap_remove;
    policy->Referenced = NULL;
    policy->Dereferenced = heap_referenced;
    policy->WalkInit = heap_walkInit;
    policy->PurgeInit = heap_purgeInit;
    /* Increase policy usage count */
    nr_heap_policies += 0;
    return policy;
}
