|
|
|
@ -1,15 +1,14 @@ |
|
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
|
* |
|
|
|
|
* freelist.c |
|
|
|
|
* routines for manipulating the buffer pool's replacement strategy |
|
|
|
|
* freelist. |
|
|
|
|
* 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.33 2003/11/13 05:34:58 wieck Exp $ |
|
|
|
|
* $Header: /cvsroot/pgsql/src/backend/storage/buffer/freelist.c,v 1.34 2003/11/13 14:57:15 wieck Exp $ |
|
|
|
|
* |
|
|
|
|
*------------------------------------------------------------------------- |
|
|
|
|
*/ |
|
|
|
@ -31,55 +30,840 @@ |
|
|
|
|
#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 |
|
|
|
|
|
|
|
|
|
static BufferDesc *SharedFreeList; |
|
|
|
|
#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; |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* State-checking macros |
|
|
|
|
* 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 */ |
|
|
|
|
|
|
|
|
|
#define IsInQueue(bf) \ |
|
|
|
|
( \
|
|
|
|
|
AssertMacro((bf->freeNext != INVALID_DESCRIPTOR)), \
|
|
|
|
|
AssertMacro((bf->freePrev != INVALID_DESCRIPTOR)), \
|
|
|
|
|
AssertMacro((bf->flags & BM_FREE)) \
|
|
|
|
|
) |
|
|
|
|
long num_lookup; /* Some hit statistics */ |
|
|
|
|
long num_hit[STRAT_NUM_LISTS]; |
|
|
|
|
time_t stat_report; |
|
|
|
|
|
|
|
|
|
#define IsNotInQueue(bf) \ |
|
|
|
|
( \
|
|
|
|
|
AssertMacro((bf->freeNext == INVALID_DESCRIPTOR)), \
|
|
|
|
|
AssertMacro((bf->freePrev == INVALID_DESCRIPTOR)), \
|
|
|
|
|
AssertMacro(! (bf->flags & BM_FREE)) \
|
|
|
|
|
) |
|
|
|
|
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 BufferStrategyStatInterval = 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] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* AddBufferToFreelist |
|
|
|
|
* 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 |
|
|
|
|
* |
|
|
|
|
* In theory, this is the only routine that needs to be changed |
|
|
|
|
* if the buffer replacement strategy changes. Just change |
|
|
|
|
* the manner in which buffers are added to the freelist queue. |
|
|
|
|
* Currently, they are added on an LRU basis. |
|
|
|
|
* 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. |
|
|
|
|
*/ |
|
|
|
|
static void |
|
|
|
|
AddBufferToFreelist(BufferDesc *bf) |
|
|
|
|
BufferDesc * |
|
|
|
|
StrategyBufferLookup(BufferTag *tagPtr, bool recheck) |
|
|
|
|
{ |
|
|
|
|
BufferStrategyCDB *cdb; |
|
|
|
|
time_t now; |
|
|
|
|
|
|
|
|
|
if (BufferStrategyStatInterval > 0) |
|
|
|
|
{ |
|
|
|
|
time(&now); |
|
|
|
|
if (StrategyControl->stat_report + BufferStrategyStatInterval < now) |
|
|
|
|
{ |
|
|
|
|
long all_hit, b1_hit, t1_hit, t2_hit, b2_hit; |
|
|
|
|
ErrorContextCallback *errcxtold; |
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
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) |
|
|
|
|
{ |
|
|
|
|
#ifdef BMTRACE |
|
|
|
|
_bm_trace(bf->tag.relId.dbId, bf->tag.relId.relId, bf->tag.blockNum, |
|
|
|
|
BufferDescriptorGetBuffer(bf), BMT_DEALLOC); |
|
|
|
|
#endif /* BMTRACE */ |
|
|
|
|
IsNotInQueue(bf); |
|
|
|
|
|
|
|
|
|
/* change bf so it points to inFrontOfNew and its successor */ |
|
|
|
|
bf->freePrev = SharedFreeList->freePrev; |
|
|
|
|
bf->freeNext = Free_List_Descriptor; |
|
|
|
|
|
|
|
|
|
/* insert new into chain */ |
|
|
|
|
BufferDescriptors[bf->freeNext].freePrev = bf->buf_id; |
|
|
|
|
BufferDescriptors[bf->freePrev].freeNext = bf->buf_id; |
|
|
|
|
int cdb_id; |
|
|
|
|
int buf_id; |
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
@ -95,18 +879,9 @@ PinBuffer(BufferDesc *buf) |
|
|
|
|
|
|
|
|
|
if (buf->refcount == 0) |
|
|
|
|
{ |
|
|
|
|
IsInQueue(buf); |
|
|
|
|
|
|
|
|
|
/* remove from freelist queue */ |
|
|
|
|
BufferDescriptors[buf->freeNext].freePrev = buf->freePrev; |
|
|
|
|
BufferDescriptors[buf->freePrev].freeNext = buf->freeNext; |
|
|
|
|
buf->freeNext = buf->freePrev = INVALID_DESCRIPTOR; |
|
|
|
|
|
|
|
|
|
/* mark buffer as no longer free */ |
|
|
|
|
buf->flags &= ~BM_FREE; |
|
|
|
|
} |
|
|
|
|
else |
|
|
|
|
IsNotInQueue(buf); |
|
|
|
|
|
|
|
|
|
if (PrivateRefCount[b] == 0) |
|
|
|
|
buf->refcount++; |
|
|
|
@ -144,7 +919,6 @@ UnpinBuffer(BufferDesc *buf) |
|
|
|
|
{ |
|
|
|
|
int b = BufferDescriptorGetBuffer(buf) - 1; |
|
|
|
|
|
|
|
|
|
IsNotInQueue(buf); |
|
|
|
|
Assert(buf->refcount > 0); |
|
|
|
|
Assert(PrivateRefCount[b] > 0); |
|
|
|
|
PrivateRefCount[b]--; |
|
|
|
@ -154,7 +928,6 @@ UnpinBuffer(BufferDesc *buf) |
|
|
|
|
if (buf->refcount == 0) |
|
|
|
|
{ |
|
|
|
|
/* buffer is now unpinned */ |
|
|
|
|
AddBufferToFreelist(buf); |
|
|
|
|
buf->flags |= BM_FREE; |
|
|
|
|
} |
|
|
|
|
else if ((buf->flags & BM_PIN_COUNT_WAITER) != 0 && |
|
|
|
@ -187,64 +960,6 @@ refcount = %ld, file: %s, line: %d\n", |
|
|
|
|
} |
|
|
|
|
#endif |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* GetFreeBuffer() -- get the 'next' buffer from the freelist. |
|
|
|
|
*/ |
|
|
|
|
BufferDesc * |
|
|
|
|
GetFreeBuffer(void) |
|
|
|
|
{ |
|
|
|
|
BufferDesc *buf; |
|
|
|
|
|
|
|
|
|
if (Free_List_Descriptor == SharedFreeList->freeNext) |
|
|
|
|
{ |
|
|
|
|
/* queue is empty. All buffers in the buffer pool are pinned. */ |
|
|
|
|
ereport(ERROR, |
|
|
|
|
(errcode(ERRCODE_INSUFFICIENT_RESOURCES), |
|
|
|
|
errmsg("out of free buffers"))); |
|
|
|
|
return NULL; |
|
|
|
|
} |
|
|
|
|
buf = &(BufferDescriptors[SharedFreeList->freeNext]); |
|
|
|
|
|
|
|
|
|
/* remove from freelist queue */ |
|
|
|
|
BufferDescriptors[buf->freeNext].freePrev = buf->freePrev; |
|
|
|
|
BufferDescriptors[buf->freePrev].freeNext = buf->freeNext; |
|
|
|
|
buf->freeNext = buf->freePrev = INVALID_DESCRIPTOR; |
|
|
|
|
|
|
|
|
|
buf->flags &= ~(BM_FREE); |
|
|
|
|
|
|
|
|
|
return buf; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* InitFreeList -- initialize the dummy buffer descriptor used |
|
|
|
|
* as a freelist head. |
|
|
|
|
* |
|
|
|
|
* Assume: All of the buffers are already linked in a circular |
|
|
|
|
* queue. Only called by postmaster and only during |
|
|
|
|
* initialization. |
|
|
|
|
*/ |
|
|
|
|
void |
|
|
|
|
InitFreeList(bool init) |
|
|
|
|
{ |
|
|
|
|
SharedFreeList = &(BufferDescriptors[Free_List_Descriptor]); |
|
|
|
|
|
|
|
|
|
if (init) |
|
|
|
|
{ |
|
|
|
|
/* we only do this once, normally in the postmaster */ |
|
|
|
|
SharedFreeList->data = INVALID_OFFSET; |
|
|
|
|
SharedFreeList->flags = 0; |
|
|
|
|
SharedFreeList->flags &= ~(BM_VALID | BM_DELETED | BM_FREE); |
|
|
|
|
SharedFreeList->buf_id = Free_List_Descriptor; |
|
|
|
|
|
|
|
|
|
/* insert it into a random spot in the circular queue */ |
|
|
|
|
SharedFreeList->freeNext = BufferDescriptors[0].freeNext; |
|
|
|
|
SharedFreeList->freePrev = 0; |
|
|
|
|
BufferDescriptors[SharedFreeList->freeNext].freePrev = |
|
|
|
|
BufferDescriptors[SharedFreeList->freePrev].freeNext = |
|
|
|
|
Free_List_Descriptor; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* print out the free list and check for breaks. |
|
|
|
|