/*-------------------------------------------------------------------------
 *
 * freelist.c
 *	  routines for manipulating the buffer pool's replacement strategy.
 *
 * Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  $Header: /cvsroot/pgsql/src/backend/storage/buffer/freelist.c,v 1.37 2003/11/27 18:12:50 petere Exp $
 *
 *-------------------------------------------------------------------------
 */
/*
 * OLD COMMENTS
 *
 * Data Structures:
 *		SharedFreeList is a circular queue.  Notice that this
 *		is a shared memory queue so the next/prev "ptrs" are
 *		buffer ids, not addresses.
 *
 * Sync: all routines in this file assume that the buffer
 *		semaphore has been acquired by the caller.
 */

#include "postgres.h"

#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/proc.h"
#include "access/xact.h"

#define STRAT_LIST_UNUSED	-1
#define STRAT_LIST_B1		0
#define STRAT_LIST_T1		1
#define STRAT_LIST_T2		2
#define STRAT_LIST_B2		3
#define STRAT_NUM_LISTS		4

#ifndef MAX
#define MAX(a,b) (((a) > (b)) ? (a) : (b))
#endif
#ifndef MIN
#define MIN(a,b) (((a) < (b)) ? (a) : (b))
#endif

/*
 * The Cache Directory Block (CDB) of the Adaptive Replacement Cache (ARC)
 */
typedef struct bufstratcdb
{
	int				prev;		/* links in the queue */
	int				next;
	int				list;		/* current list */
	BufferTag		buf_tag;	/* buffer key */
	Buffer			buf_id;		/* currently assigned data buffer */
	TransactionId	t1_xid;		/* the xid this entry went onto T1 */
} BufferStrategyCDB;

/*
 * The shared ARC control information.
 */
typedef struct bufstratcontrol
{

	int		target_T1_size;				/* What T1 size are we aiming for */
	int		listUnusedCDB;				/* All unused StrategyCDB */
	int		listHead[STRAT_NUM_LISTS];	/* ARC lists B1, T1, T2 and B2 */
	int		listTail[STRAT_NUM_LISTS];
	int		listSize[STRAT_NUM_LISTS];
	Buffer	listFreeBuffers;			/* List of unused buffers */

	long	num_lookup;					/* Some hit statistics */
	long	num_hit[STRAT_NUM_LISTS];
	time_t	stat_report;

	BufferStrategyCDB	cdb[1];			/* The cache directory */
} BufferStrategyControl;
 

static BufferStrategyControl	*StrategyControl = NULL;
static BufferStrategyCDB		*StrategyCDB = NULL;

static int		strategy_cdb_found;
static int		strategy_cdb_replace;
static int		strategy_get_from;

int				DebugSharedBuffers = 0;

static bool				strategy_hint_vacuum;
static TransactionId	strategy_vacuum_xid;


#define T1_TARGET	StrategyControl->target_T1_size
#define B1_LENGTH	StrategyControl->listSize[STRAT_LIST_B1]
#define T1_LENGTH	StrategyControl->listSize[STRAT_LIST_T1]
#define T2_LENGTH	StrategyControl->listSize[STRAT_LIST_T2]
#define B2_LENGTH	StrategyControl->listSize[STRAT_LIST_B2]


/*
 * Macro to remove a CDB from whichever list it currently is on
 */
#define	STRAT_LIST_REMOVE(cdb) \
{ \
	AssertMacro((cdb)->list >= 0 && (cdb)->list < STRAT_NUM_LISTS);		\
	if ((cdb)->prev < 0)												\
		StrategyControl->listHead[(cdb)->list] = (cdb)->next;			\
	else																\
		StrategyCDB[(cdb)->prev].next = (cdb)->next;					\
	if ((cdb)->next < 0)												\
		StrategyControl->listTail[(cdb)->list] = (cdb)->prev;			\
	else																\
		StrategyCDB[(cdb)->next].prev = (cdb)->prev;					\
	StrategyControl->listSize[(cdb)->list]--;							\
	(cdb)->list = STRAT_LIST_UNUSED;									\
}

/*
 * Macro to add a CDB to the tail of a list (MRU position)
 */
#define STRAT_MRU_INSERT(cdb,l) \
{ \
	AssertMacro((cdb)->list == STRAT_LIST_UNUSED);						\
	if (StrategyControl->listTail[(l)] < 0)								\
	{																	\
		(cdb)->prev = (cdb)->next = -1;									\
		StrategyControl->listHead[(l)] = 								\
			StrategyControl->listTail[(l)] =							\
			((cdb) - StrategyCDB);										\
	}																	\
	else																\
	{																	\
		(cdb)->next = -1;												\
		(cdb)->prev = StrategyControl->listTail[(l)];					\
		StrategyCDB[StrategyControl->listTail[(l)]].next = 				\
			((cdb) - StrategyCDB);										\
		StrategyControl->listTail[(l)] = 								\
			((cdb) - StrategyCDB);										\
	}																	\
	StrategyControl->listSize[(l)]++;									\
	(cdb)->list = (l);													\
}

/*
 * Macro to add a CDB to the head of a list (LRU position)
 */
#define STRAT_LRU_INSERT(cdb,l) \
{ \
	AssertMacro((cdb)->list == STRAT_LIST_UNUSED);						\
	if (StrategyControl->listHead[(l)] < 0)								\
	{																	\
		(cdb)->prev = (cdb)->next = -1;									\
		StrategyControl->listHead[(l)] = 								\
			StrategyControl->listTail[(l)] =							\
			((cdb) - StrategyCDB);										\
	}																	\
	else																\
	{																	\
		(cdb)->prev = -1;												\
		(cdb)->next = StrategyControl->listHead[(l)];					\
		StrategyCDB[StrategyControl->listHead[(l)]].prev = 				\
			((cdb) - StrategyCDB);										\
		StrategyControl->listHead[(l)] = 								\
			((cdb) - StrategyCDB);										\
	}																	\
	StrategyControl->listSize[(l)]++;									\
	(cdb)->list = (l);													\
}


/*
 * StrategyBufferLookup
 *
 *	Lookup a page request in the cache directory. A buffer is only
 *	returned for a T1 or T2 cache hit. B1 and B2 hits are only
 *	remembered here to later affect the behaviour.
 */
BufferDesc *
StrategyBufferLookup(BufferTag *tagPtr, bool recheck)
{
	BufferStrategyCDB  *cdb;
	time_t				now;

	if (DebugSharedBuffers > 0)
	{
		time(&now);
		if (StrategyControl->stat_report + DebugSharedBuffers < now)
		{
			long	all_hit, b1_hit, t1_hit, t2_hit, b2_hit;
			int		id, t1_clean, t2_clean;
			ErrorContextCallback	*errcxtold;

			id = StrategyControl->listHead[STRAT_LIST_T1];
			t1_clean = 0;
			while (id >= 0)
			{
				if (BufferDescriptors[StrategyCDB[id].buf_id].flags & BM_DIRTY)
					break;
				t1_clean++;
				id = StrategyCDB[id].next;
			}
			id = StrategyControl->listHead[STRAT_LIST_T2];
			t2_clean = 0;
			while (id >= 0)
			{
				if (BufferDescriptors[StrategyCDB[id].buf_id].flags & BM_DIRTY)
					break;
				t2_clean++;
				id = StrategyCDB[id].next;
			}

			if (StrategyControl->num_lookup == 0)
			{
				all_hit = b1_hit = t1_hit = t2_hit = b2_hit = 0;
			}
			else
			{
				b1_hit = (StrategyControl->num_hit[STRAT_LIST_B1] * 100 /
						  StrategyControl->num_lookup);
				t1_hit = (StrategyControl->num_hit[STRAT_LIST_T1] * 100 /
						  StrategyControl->num_lookup);
				t2_hit = (StrategyControl->num_hit[STRAT_LIST_T2] * 100 /
						  StrategyControl->num_lookup);
				b2_hit = (StrategyControl->num_hit[STRAT_LIST_B2] * 100 /
						  StrategyControl->num_lookup);
				all_hit = b1_hit + t1_hit + t2_hit + b2_hit;
			}

			errcxtold = error_context_stack;
			error_context_stack = NULL;
			elog(DEBUG1, "ARC T1target=%5d B1len=%5d T1len=%5d T2len=%5d B2len=%5d",
					T1_TARGET, B1_LENGTH, T1_LENGTH, T2_LENGTH, B2_LENGTH);
			elog(DEBUG1, "ARC total   =%4ld%% B1hit=%4ld%% T1hit=%4ld%% T2hit=%4ld%% B2hit=%4ld%%",
					all_hit, b1_hit, t1_hit, t2_hit, b2_hit);
			elog(DEBUG1, "ARC clean buffers at LRU       T1=   %5d T2=   %5d",
					t1_clean, t2_clean);
			error_context_stack = errcxtold;

			StrategyControl->num_lookup = 0;
			StrategyControl->num_hit[STRAT_LIST_B1] = 0;
			StrategyControl->num_hit[STRAT_LIST_T1] = 0;
			StrategyControl->num_hit[STRAT_LIST_T2] = 0;
			StrategyControl->num_hit[STRAT_LIST_B2] = 0;
			StrategyControl->stat_report = now;
		}
	}

	/*
	 * Count lookups
	 */
	StrategyControl->num_lookup++;

	/*
	 * Lookup the block in the shared hash table
	 */
	strategy_cdb_found = BufTableLookup(tagPtr);

	/*
	 * Handle CDB lookup miss
	 */
	if (strategy_cdb_found < 0)
	{
		if (!recheck)
		{
			/*
			 * This is an initial lookup and we have a complete
			 * cache miss (block found nowhere). This means we
			 * remember according to the current T1 size and the
			 * target T1 size from where we take a block if we
			 * need one later.
			 */
			if (T1_LENGTH >= MAX(1, T1_TARGET))
				strategy_get_from = STRAT_LIST_T1;
			else
				strategy_get_from = STRAT_LIST_T2;
		}

		/* report cache miss */
		return NULL;
	}

	/*
	 * We found a CDB
	 */
	cdb = &StrategyCDB[strategy_cdb_found];

	/*
	 * Count hits
	 */
	StrategyControl->num_hit[cdb->list]++;

	/*
	 * If this is a T2 hit, we simply move the CDB to the
	 * T2 MRU position and return the found buffer.
	 */
	if (cdb->list == STRAT_LIST_T2)
	{
		STRAT_LIST_REMOVE(cdb);
		STRAT_MRU_INSERT(cdb, STRAT_LIST_T2);

		return &BufferDescriptors[cdb->buf_id];
	}

	/*
	 * If this is a T1 hit, we move the buffer to the T2 MRU
	 * only if another transaction had read it into T1. This is
	 * required because any UPDATE or DELETE in PostgreSQL does
	 * multiple ReadBuffer(), first during the scan, later during
	 * the heap_update() or heap_delete().
	 */
	if (cdb->list == STRAT_LIST_T1)
	{
		if (!TransactionIdIsCurrentTransactionId(cdb->t1_xid))
		{
			STRAT_LIST_REMOVE(cdb);
			STRAT_MRU_INSERT(cdb, STRAT_LIST_T2);
		}

		return &BufferDescriptors[cdb->buf_id];
	}

	/*
	 * In the case of a recheck we don't care about B1 or B2 hits here.
	 * The bufmgr does this call only to make sure noone faulted in the
	 * block while we where busy flushing another. Now for this really
	 * to end up as a B1 or B2 cache hit, we must have been flushing for
	 * quite some time as the block not only must have been read, but
	 * also traveled through the queue and evicted from the T cache again
	 * already. 
	 */
	if (recheck)
	{
		return NULL;
	}

	/*
	 * Adjust the target size of the T1 cache depending on if this is
	 * a B1 or B2 hit.
	 */
	switch (cdb->list)
	{
		case STRAT_LIST_B1:
			/*
			 * B1 hit means that the T1 cache is probably too
			 * small. Adjust the T1 target size and continue
			 * below.
			 */
			T1_TARGET = MIN(T1_TARGET + MAX(B2_LENGTH / B1_LENGTH, 1),
							Data_Descriptors);
			break;

		case STRAT_LIST_B2:
			/* 
			 * B2 hit means that the T2 cache is probably too
			 * small. Adjust the T1 target size and continue
			 * below.
 */
			T1_TARGET = MAX(T1_TARGET - MAX(B1_LENGTH / B2_LENGTH, 1), 0);
			break;

		default:
			elog(ERROR, "Buffer hash table corrupted - CDB on list %d found",
					cdb->list);
	}

	/*
	 * Decide where to take from if we will be out of
	 * free blocks later in StrategyGetBuffer().
	 */
	if (T1_LENGTH >= MAX(1, T1_TARGET))
		strategy_get_from = STRAT_LIST_T1;
	else
		strategy_get_from = STRAT_LIST_T2;

	/*
	 * Even if we had seen the block in the past, it's data is
	 * not currently in memory ... cache miss to the bufmgr.
	 */
	return NULL;
}


/*
 * StrategyGetBuffer
 *
 *	Called by the bufmgr to get the next candidate buffer to use in
 *	BufferAlloc(). The only hard requirement BufferAlloc() has is that
 *	this buffer must not currently be pinned. 
 */
BufferDesc *
StrategyGetBuffer(void)
{
	int				cdb_id;
	BufferDesc	   *buf;

	if (StrategyControl->listFreeBuffers < 0)
	{
		/* We don't have a free buffer, must take one from T1 or T2 */

		if (strategy_get_from == STRAT_LIST_T1)
		{
			/*
			 * We should take the first unpinned buffer from T1.
			 */
			cdb_id = StrategyControl->listHead[STRAT_LIST_T1];
			while (cdb_id >= 0)
			{
				buf = &BufferDescriptors[StrategyCDB[cdb_id].buf_id];
				if (buf->refcount == 0)
				{
					strategy_cdb_replace = cdb_id;
					Assert(StrategyCDB[cdb_id].list == STRAT_LIST_T1);
					return buf;
				}
				cdb_id = StrategyCDB[cdb_id].next;
			}

			/*
			 * No unpinned T1 buffer found - pardon T2 cache.
			 */
			cdb_id = StrategyControl->listHead[STRAT_LIST_T2];
			while (cdb_id >= 0)
			{
				buf = &BufferDescriptors[StrategyCDB[cdb_id].buf_id];
				if (buf->refcount == 0)
				{
					strategy_cdb_replace = cdb_id;
					Assert(StrategyCDB[cdb_id].list == STRAT_LIST_T2);
					return buf;
				}
				cdb_id = StrategyCDB[cdb_id].next;
			}

			/*
			 * No unpinned buffers at all!!!
			 */
			elog(ERROR, "StrategyGetBuffer(): Out of unpinned buffers");
		}
		else
		{
			/*
			 * We should take the first unpinned buffer from T2.
			 */
			cdb_id = StrategyControl->listHead[STRAT_LIST_T2];
			while (cdb_id >= 0)
			{
				buf = &BufferDescriptors[StrategyCDB[cdb_id].buf_id];
				if (buf->refcount == 0)
				{
					strategy_cdb_replace = cdb_id;
					Assert(StrategyCDB[cdb_id].list == STRAT_LIST_T2);
					return buf;
				}
				cdb_id = StrategyCDB[cdb_id].next;
			}

			/*
			 * No unpinned T2 buffer found - pardon T1 cache.
			 */
			cdb_id = StrategyControl->listHead[STRAT_LIST_T1];
			while (cdb_id >= 0)
			{
				buf = &BufferDescriptors[StrategyCDB[cdb_id].buf_id];
				if (buf->refcount == 0)
				{
					strategy_cdb_replace = cdb_id;
					Assert(StrategyCDB[cdb_id].list == STRAT_LIST_T1);
					return buf;
				}
				cdb_id = StrategyCDB[cdb_id].next;
			}

			/*
			 * No unpinned buffers at all!!!
			 */
			elog(ERROR, "StrategyGetBuffer(): Out of unpinned buffers");
		}
	}
	else
	{
		/* There is a completely free buffer available - take it */

		/*
		 * Note: This code uses the side effect that a free buffer
		 * can never be pinned or dirty and therefore the call to
		 * StrategyReplaceBuffer() will happen without the bufmgr
		 * releasing the bufmgr-lock in the meantime. That means,
		 * that there will never be any reason to recheck. Otherwise
		 * we would leak shared buffers here!
		 */
		strategy_cdb_replace = -1;
		buf = &BufferDescriptors[StrategyControl->listFreeBuffers];

		StrategyControl->listFreeBuffers = buf->bufNext;
		buf->bufNext = -1;

		/* Buffer of freelist cannot be pinned */
		Assert(buf->refcount == 0);
		Assert(!(buf->flags & BM_DIRTY));

		return buf;
	}

	/* not reached */
	return NULL;
}


/*
 * StrategyReplaceBuffer
 *
 *	Called by the buffer manager to inform us that he possibly flushed
 * 	a buffer and is now about to replace the content. Prior to this call,
 *	the cache algorithm still reports the buffer as in the cache. After
 *	this call we report the new block, even if IO might still need to
 *	start.
 */
void
StrategyReplaceBuffer(BufferDesc *buf, Relation rnode, BlockNumber blockNum)
{
	BufferStrategyCDB	   *cdb_found;
	BufferStrategyCDB	   *cdb_replace;

	if (strategy_cdb_found >= 0)
	{
		/* This was a ghost buffer cache hit (B1 or B2) */
		cdb_found = &StrategyCDB[strategy_cdb_found];

		/* Assert that the buffer remembered in cdb_found is the one */
		/* the buffer manager is currently faulting in */
		Assert(BUFFERTAG_EQUALS(&(cdb_found->buf_tag), rnode, blockNum));
		
		if (strategy_cdb_replace >= 0)
		{
			/* We are satisfying it with an evicted T buffer */
			cdb_replace = &StrategyCDB[strategy_cdb_replace];

			/* Assert that the buffer remembered in cdb_replace is */
			/* the one the buffer manager has just evicted */
			Assert(cdb_replace->list == STRAT_LIST_T1 || 
					cdb_replace->list == STRAT_LIST_T2);
			Assert(cdb_replace->buf_id == buf->buf_id);
			Assert(BUFFERTAGS_EQUAL(&(cdb_replace->buf_tag), &(buf->tag)));

			/* If this was a T1 buffer faulted in by vacuum, just */
			/* do not cause the CDB end up in the B1 list, so that */
			/* the vacuum scan does not affect T1_target adjusting */
			if (strategy_hint_vacuum)
			{
				BufTableDelete(&(cdb_replace->buf_tag));
				STRAT_LIST_REMOVE(cdb_replace);
				cdb_replace->buf_id = -1;
				cdb_replace->next = StrategyControl->listUnusedCDB;
				StrategyControl->listUnusedCDB = strategy_cdb_replace;
			}
			else
			{
				/* Under normal circumstances move the evicted */
				/* T list entry to it's corresponding B list */
				if (cdb_replace->list == STRAT_LIST_T1)
				{
					STRAT_LIST_REMOVE(cdb_replace);
					STRAT_MRU_INSERT(cdb_replace, STRAT_LIST_B1);
				}
				else
				{
					STRAT_LIST_REMOVE(cdb_replace);
					STRAT_MRU_INSERT(cdb_replace, STRAT_LIST_B2);
				}
			}
			/* And clear it's block reference */
			cdb_replace->buf_id = -1;
		}
		else
		{
			/* or we satisfy it with an unused buffer */
		}

		/* Now the found B CDB get's the buffer and is moved to T2 */
		cdb_found->buf_id = buf->buf_id;
		STRAT_LIST_REMOVE(cdb_found);
		STRAT_MRU_INSERT(cdb_found, STRAT_LIST_T2);
	}
	else
	{
		/* This was a complete cache miss, so we need to create */
		/* a new CDB. The goal is to keep T1len+B1len <= c */

		if (B1_LENGTH > 0 && (T1_LENGTH + B1_LENGTH) >= Data_Descriptors)
		{
			/* So if B1 isn't empty and T1len+B1len >= c we take B1-LRU */
			cdb_found = &StrategyCDB[StrategyControl->listHead[STRAT_LIST_B1]];

			BufTableDelete(&(cdb_found->buf_tag));
			STRAT_LIST_REMOVE(cdb_found);
		}
		else
		{
			/* Otherwise, we try to use a free one */
			if (StrategyControl->listUnusedCDB >= 0)
			{
				cdb_found = &StrategyCDB[StrategyControl->listUnusedCDB];
				StrategyControl->listUnusedCDB = cdb_found->next;
			}
			else
			{
				/* If there isn't, we take B2-LRU ... except if */
				/* T1len+B1len+T2len = c ... oh my */
				if (B2_LENGTH > 0)
					cdb_found = &StrategyCDB[StrategyControl->listHead[STRAT_LIST_B2]];
				else
					cdb_found = &StrategyCDB[StrategyControl->listHead[STRAT_LIST_B1]];

				BufTableDelete(&(cdb_found->buf_tag));
				STRAT_LIST_REMOVE(cdb_found);
			}
		}

		/* Set the CDB's buf_tag and insert the hash key */
		INIT_BUFFERTAG(&(cdb_found->buf_tag), rnode, blockNum);
		BufTableInsert(&(cdb_found->buf_tag), (cdb_found - StrategyCDB));

		if (strategy_cdb_replace >= 0)
		{
			/* The buffer was formerly in a T list, move it's CDB
			 * to the corresponding B list */
			cdb_replace = &StrategyCDB[strategy_cdb_replace];

			Assert(cdb_replace->list == STRAT_LIST_T1 || 
					cdb_replace->list == STRAT_LIST_T2);
			Assert(cdb_replace->buf_id == buf->buf_id);
			Assert(BUFFERTAGS_EQUAL(&(cdb_replace->buf_tag), &(buf->tag)));

			if (cdb_replace->list == STRAT_LIST_T1)
			{
				STRAT_LIST_REMOVE(cdb_replace);
				STRAT_MRU_INSERT(cdb_replace, STRAT_LIST_B1);
			}
			else
			{
				STRAT_LIST_REMOVE(cdb_replace);
				STRAT_MRU_INSERT(cdb_replace, STRAT_LIST_B2);
			}
			/* And clear it's block reference */
			cdb_replace->buf_id = -1;
		}
		else
		{
			/* or we satisfy it with an unused buffer */
		}

		/* Assign the buffer id to the new CDB */
		cdb_found->buf_id = buf->buf_id;

		/*
		 * Specialized VACUUM optimization. If this "complete cache miss"
		 * happened because vacuum needed the page, we want it later on
		 * to be placed at the LRU instead of the MRU position of T1.
		 */
		if (strategy_hint_vacuum)
		{
			if (strategy_vacuum_xid != GetCurrentTransactionId())
			{
				strategy_hint_vacuum = false;
				STRAT_MRU_INSERT(cdb_found, STRAT_LIST_T1);
			}
			else
				STRAT_LRU_INSERT(cdb_found, STRAT_LIST_T1);
			
		}
		else
			STRAT_MRU_INSERT(cdb_found, STRAT_LIST_T1);

		/*
		 * Remember the Xid when this buffer went onto T1 to avoid
		 * a single UPDATE promoting a newcomer straight into T2.
		 */
		cdb_found->t1_xid = GetCurrentTransactionId();
	}
}


/*
 * StrategyInvalidateBuffer
 *
 *	Called by the buffer manager to inform us that a buffer content
 *	is no longer valid. We simply throw away any eventual existing
 *	buffer hash entry and move the CDB and buffer to the free lists.
 */
void
StrategyInvalidateBuffer(BufferDesc *buf)
{
	int					cdb_id;
#ifdef USE_ASSERT_CHECKING
	int					buf_id;
#endif
	BufferStrategyCDB  *cdb;

	/* The buffer cannot be dirty or pinned */
	Assert(!(buf->flags & BM_DIRTY));
	Assert(buf->refcount == 0);

	/*
	 * If we have the buffer somewhere in the directory, remove it,
	 * add the CDB to the list of unused CDB's. and the buffer to
	 * the list of free buffers
	 */
	cdb_id = BufTableLookup(&(buf->tag));
	if (cdb_id >= 0)
	{
		cdb = &StrategyCDB[cdb_id];
		BufTableDelete(&(cdb->buf_tag));
		STRAT_LIST_REMOVE(cdb);
		cdb->buf_id = -1;
		cdb->next = StrategyControl->listUnusedCDB;
		StrategyControl->listUnusedCDB = cdb_id;

		buf->bufNext = StrategyControl->listFreeBuffers;
		StrategyControl->listFreeBuffers = buf->buf_id;
		return;
	}

#ifdef USE_ASSERT_CHECKING
	/*
	 * Check that we have this buffer in the freelist already.
	 */
	buf_id = StrategyControl->listFreeBuffers;
	while (buf_id >= 0)
	{
		if (buf == &BufferDescriptors[buf_id])
			return;

		buf_id = BufferDescriptors[buf_id].bufNext;
	}

	elog(ERROR, "StrategyInvalidateBuffer() buffer %d not in directory or freelist",
				buf->buf_id);
#endif
}


void
StrategyHintVacuum(bool vacuum_active)
{
	strategy_hint_vacuum = vacuum_active;
	strategy_vacuum_xid = GetCurrentTransactionId();
}


int
StrategyDirtyBufferList(int *buffer_list, int max_buffers)
{
	int					num_buffer_dirty = 0;
	int					cdb_id_t1;
	int					cdb_id_t2;
	int					buf_id;
	BufferDesc		   *buf;

	/*
	 * Traverse the T1 and T2 list LRU to MRU in "parallel"
	 * and add all dirty buffers found in that order to the list.
	 * The ARC strategy keeps all used buffers including pinned ones
	 * in the T1 or T2 list. So we cannot loose any dirty buffers.
	 */
	cdb_id_t1 = StrategyControl->listHead[STRAT_LIST_T1];
	cdb_id_t2 = StrategyControl->listHead[STRAT_LIST_T2];

	while ((cdb_id_t1 >= 0 || cdb_id_t2 >= 0) && 
			num_buffer_dirty < max_buffers)
	{
		if (cdb_id_t1 >= 0)
		{
			buf_id = StrategyCDB[cdb_id_t1].buf_id;
			buf = &BufferDescriptors[buf_id];

			if (buf->flags & BM_VALID)
			{
				if ((buf->flags & BM_DIRTY) || (buf->cntxDirty))
				{
					buffer_list[num_buffer_dirty++] = buf_id;
				}
			}

			cdb_id_t1 = StrategyCDB[cdb_id_t1].next;
		}

		if (cdb_id_t2 >= 0)
		{
			buf_id = StrategyCDB[cdb_id_t2].buf_id;
			buf = &BufferDescriptors[buf_id];

			if (buf->flags & BM_VALID)
			{
				if ((buf->flags & BM_DIRTY) || (buf->cntxDirty))
				{
					buffer_list[num_buffer_dirty++] = buf_id;
				}
			}

			cdb_id_t2 = StrategyCDB[cdb_id_t2].next;
		}
	}

	return num_buffer_dirty;
}


/*
 * StrategyInitialize -- initialize the buffer cache replacement
 *		strategy.
 *
 * Assume: All of the buffers are already building a linked list.
 *		Only called by postmaster and only during initialization.
 */
void
StrategyInitialize(bool init)
{
	bool found;
	int i;

	/*
	 * Initialize the shared CDB lookup hashtable
	 */
	InitBufTable(Data_Descriptors * 2);

	/*
	 * Get or create the shared strategy control block and the CDB's
	 */
	StrategyControl = (BufferStrategyControl *)
			ShmemInitStruct("Buffer Strategy Status",
					sizeof(BufferStrategyControl) +
					sizeof(BufferStrategyCDB) * (Data_Descriptors * 2 - 1),
					&found);
	StrategyCDB = &(StrategyControl->cdb[0]);

	if (!found)
	{
		/*
		 * Only done once, usually in postmaster
		 */
		Assert(init);

		/*
		 * Grab the whole linked list of free buffers for our
		 * strategy
		 */
		StrategyControl->listFreeBuffers = 0;

		/*
		 * We start off with a target T1 list size of
		 * half the available cache blocks.
		 */
		StrategyControl->target_T1_size = Data_Descriptors / 2;

		/*
		 * Initialize B1, T1, T2 and B2 lists to be empty
		 */
		for (i = 0; i < STRAT_NUM_LISTS; i++)
		{
			StrategyControl->listHead[i] = -1;
			StrategyControl->listTail[i] = -1;
			StrategyControl->listSize[i] = 0;
			StrategyControl->num_hit[i] = 0;
		}
		StrategyControl->num_lookup  = 0;
		StrategyControl->stat_report = 0;

		/*
		 * All CDB's are linked as the listUnusedCDB
		 */
		for (i = 0; i < Data_Descriptors * 2; i++)
		{
			StrategyCDB[i].next = i + 1;
			StrategyCDB[i].list = STRAT_LIST_UNUSED;
			CLEAR_BUFFERTAG(&(StrategyCDB[i].buf_tag));
			StrategyCDB[i].buf_id = -1;
		}
		StrategyCDB[Data_Descriptors * 2 - 1].next = -1;
		StrategyControl->listUnusedCDB = 0;
	}
	else
	{
		Assert(!init);
	}
}


#undef PinBuffer

/*
 * PinBuffer -- make buffer unavailable for replacement.
 *
 * This should be applied only to shared buffers, never local ones.
 * Bufmgr lock must be held by caller.
 */
void
PinBuffer(BufferDesc *buf)
{
	int			b = BufferDescriptorGetBuffer(buf) - 1;

	if (buf->refcount == 0)
	{
		/* mark buffer as no longer free */
		buf->flags &= ~BM_FREE;
	}

	if (PrivateRefCount[b] == 0)
		buf->refcount++;
	PrivateRefCount[b]++;
	Assert(PrivateRefCount[b] > 0);
}

#ifdef NOT_USED
void
PinBuffer_Debug(char *file, int line, BufferDesc *buf)
{
	PinBuffer(buf);
	if (ShowPinTrace)
	{
		Buffer		buffer = BufferDescriptorGetBuffer(buf);

		fprintf(stderr, "PIN(Pin) %ld relname = %s, blockNum = %d, \
refcount = %ld, file: %s, line: %d\n",
				buffer, buf->blind.relname, buf->tag.blockNum,
				PrivateRefCount[buffer - 1], file, line);
	}
}
#endif

#undef UnpinBuffer

/*
 * UnpinBuffer -- make buffer available for replacement.
 *
 * This should be applied only to shared buffers, never local ones.
 * Bufmgr lock must be held by caller.
 */
void
UnpinBuffer(BufferDesc *buf)
{
	int			b = BufferDescriptorGetBuffer(buf) - 1;

	Assert(buf->refcount > 0);
	Assert(PrivateRefCount[b] > 0);
	PrivateRefCount[b]--;
	if (PrivateRefCount[b] == 0)
		buf->refcount--;

	if (buf->refcount == 0)
	{
		/* buffer is now unpinned */
		buf->flags |= BM_FREE;
	}
	else if ((buf->flags & BM_PIN_COUNT_WAITER) != 0 &&
			 buf->refcount == 1)
	{
		/* we just released the last pin other than the waiter's */
		buf->flags &= ~BM_PIN_COUNT_WAITER;
		ProcSendSignal(buf->wait_backend_id);
	}
	else
	{
		/* do nothing */
	}
}

#ifdef NOT_USED
void
UnpinBuffer_Debug(char *file, int line, BufferDesc *buf)
{
	UnpinBuffer(buf);
	if (ShowPinTrace)
	{
		Buffer		buffer = BufferDescriptorGetBuffer(buf);

		fprintf(stderr, "UNPIN(Unpin) %ld relname = %s, blockNum = %d, \
refcount = %ld, file: %s, line: %d\n",
				buffer, buf->blind.relname, buf->tag.blockNum,
				PrivateRefCount[buffer - 1], file, line);
	}
}
#endif


/*
 * print out the free list and check for breaks.
 */
#ifdef NOT_USED
void
DBG_FreeListCheck(int nfree)
{
	int			i;
	BufferDesc *buf;

	buf = &(BufferDescriptors[SharedFreeList->freeNext]);
	for (i = 0; i < nfree; i++, buf = &(BufferDescriptors[buf->freeNext]))
	{
		if (!(buf->flags & (BM_FREE)))
		{
			if (buf != SharedFreeList)
				printf("\tfree list corrupted: %d flags %x\n",
					   buf->buf_id, buf->flags);
			else
				printf("\tfree list corrupted: too short -- %d not %d\n",
					   i, nfree);
		}
		if ((BufferDescriptors[buf->freeNext].freePrev != buf->buf_id) ||
			(BufferDescriptors[buf->freePrev].freeNext != buf->buf_id))
			printf("\tfree list links corrupted: %d %ld %ld\n",
				   buf->buf_id, buf->freePrev, buf->freeNext);
	}
	if (buf != SharedFreeList)
		printf("\tfree list corrupted: %d-th buffer is %d\n",
			   nfree, buf->buf_id);
}
#endif

#ifdef NOT_USED
/*
 * PrintBufferFreeList -
 *	  prints the buffer free list, for debugging
 */
static void
PrintBufferFreeList()
{
	BufferDesc *buf;

	if (SharedFreeList->freeNext == Free_List_Descriptor)
	{
		printf("free list is empty.\n");
		return;
	}

	buf = &(BufferDescriptors[SharedFreeList->freeNext]);
	for (;;)
	{
		int			i = (buf - BufferDescriptors);

		printf("[%-2d] (%s, %d) flags=0x%x, refcnt=%d %ld, nxt=%ld prv=%ld)\n",
			   i, buf->blind.relname, buf->tag.blockNum,
			   buf->flags, buf->refcount, PrivateRefCount[i],
			   buf->freeNext, buf->freePrev);

		if (buf->freeNext == Free_List_Descriptor)
			break;

		buf = &(BufferDescriptors[buf->freeNext]);
	}
}

#endif
