/** @file pageCache handles the replacement policy for buffer manager. This allows bufferManager's implementation to focus on providing atomic writes, and locking. */ #include #include #include #include #include "latches.h" #include #include #include #include "page.h" #include "pageFile.h" static pblHashTable_t *activePages; /* page lookup */ static unsigned int bufferSize; /* < MAX_BUFFER_SIZE */ static Page *repHead, *repMiddle, *repTail; /* replacement policy */ void pageCacheInit() { Page *first; bufferSize = 1; activePages = pblHtCreate(); assert(activePages); DEBUG("pageCacheInit()"); first = pageAlloc(0); pblHtInsert(activePages, &first->id, sizeof(int), first); first->prev = first->next = NULL; /* pageMap(first); */ pageRead(first); repHead = repTail = first; repMiddle = NULL; } void pageCacheDeinit() { Page *p; DEBUG("pageCacheDeinit()"); for( p = (Page*)pblHtFirst( activePages ); p; p = (Page*)pblHtRemove( activePages, 0, 0 )) { DEBUG("+"); /** @todo No one seems to set the dirty flag... */ /*if(p->dirty && (ret = pageWrite(p)/ *flushPage(*p)* /)) { printf("ERROR: flushPage on %s line %d", __FILE__, __LINE__); abort(); / * exit(ret); * / }*/ pageWrite(p); } pblHtDelete(activePages); } static void headInsert(Page *ret) { assert(ret != repMiddle); assert(ret != repTail); assert(ret != repHead); repHead->prev = ret; ret->next = repHead; ret->prev = NULL; repHead = ret; } static void middleInsert(Page *ret) { assert( bufferSize == MAX_BUFFER_SIZE ); assert(ret != repMiddle); assert(ret != repTail); assert(ret != repHead); ret->prev = repMiddle->prev; ret->next = repMiddle; repMiddle->prev = ret; ret->prev->next = ret; ret->queue = 2; repMiddle = ret; assert(ret->next != ret && ret->prev != ret); } static void qRemove(Page *ret) { assert( bufferSize == MAX_BUFFER_SIZE ); assert(ret->next != ret && ret->prev != ret); if( ret->prev ) ret->prev->next = ret->next; else /* is head */ repHead = ret->next; /* won't have head == tail because of test in loadPage */ if( ret->next ) { ret->next->prev = ret->prev; /* TODO: these if can be better organizeed for speed */ if( ret == repMiddle ) /* select new middle */ repMiddle = ret->next; } else /* is tail */ repTail = ret->prev; assert(ret != repMiddle); assert(ret != repTail); assert(ret != repHead); } static Page *kickPage(int pageid) { Page *ret = repTail; assert( bufferSize == MAX_BUFFER_SIZE ); qRemove(ret); pblHtRemove(activePages, &ret->id, sizeof(int)); /* It's almost safe to release the mutex here. The LRU-2 linked lists are in a consistent (but under-populated) state, and there is no reference to the page that we're holding in the hash table, so the data structures are internally consistent. The problem is that that loadPagePtr could be called multiple times with the same pageid, so we need to check for that, or we might load the same page into multiple cache slots, which would cause consistency problems. @todo Don't block while holding the loadPagePtr mutex! */ /*pthread_mutex_unlock(loadPagePtr_mutex);*/ pageWrite(ret); /*pthread_mutex_lock(loadPagePtr_mutex);*/ writelock(ret->rwlatch, 121); pageRealloc(ret, pageid); middleInsert(ret); /* pblHtInsert(activePages, &pageid, sizeof(int), ret); */ return ret; } int lastPageId = -1; Page * lastPage = 0; static pthread_mutex_t loadPagePtr_mutex; void *loadPagePtr(int pageid) { /* lock activePages, bufferSize */ Page *ret; pthread_mutex_lock(&(loadPagePtr_mutex)); if(lastPage && lastPageId == pageid) { void * ret = lastPage; pthread_mutex_unlock(&(loadPagePtr_mutex)); return ret; } else { ret = pblHtLookup(activePages, &pageid, sizeof(int)); } if( ret ) { /** Don't need write lock for linked list manipulations. The loadPagePtr_mutex protects those operations. */ if( bufferSize == MAX_BUFFER_SIZE ) { /* we need to worry about page sorting */ /* move to head */ if( ret != repHead ) { qRemove(ret); headInsert(ret); assert(ret->next != ret && ret->prev != ret); if( ret->queue == 2 ) { /* keep first queue same size */ repMiddle = repMiddle->prev; repMiddle->queue = 2; ret->queue = 1; } } } lastPage = ret; lastPageId = pageid; /* DEBUG("Loaded page %d => %x\n", pageid, (unsigned int) ret->memAddr); */ pthread_mutex_unlock(&(loadPagePtr_mutex)); return ret; } else if( bufferSize == MAX_BUFFER_SIZE ) { /* we need to kick */ ret = kickPage(pageid); } else if( bufferSize == MAX_BUFFER_SIZE-1 ) { /* we need to setup kickPage mechanism */ int i; Page *iter; ret = pageAlloc(pageid); writelock(ret->rwlatch, 224); headInsert(ret); assert(ret->next != ret && ret->prev != ret); /* pblHtInsert( activePages, &pageid, sizeof(int), ret ); */ bufferSize++; /* split up queue: * "in all cases studied ... fixing the primary region to 30% ... * resulted in the best performance" */ repMiddle = repHead; for( i = 0; i < MAX_BUFFER_SIZE / 3; i++ ) { repMiddle->queue = 1; repMiddle = repMiddle->next; } for( iter = repMiddle; iter; iter = iter->next ) { iter->queue = 2; } } else { /* we are adding to an nonfull queue */ bufferSize++; ret = pageAlloc(pageid); writelock(ret->rwlatch, 224); headInsert(ret); assert(ret->next != ret && ret->prev != ret); assert(ret->next != ret && ret->prev != ret); /* pblHtInsert( activePages, &pageid, sizeof(int), ret ); */ } /* we now have a page we can dump info into */ assert( ret->id == pageid ); pblHtInsert( activePages, &pageid, sizeof(int), ret ); lastPage = ret; lastPageId = pageid; /* release mutex for this function */ pthread_mutex_unlock(&(loadPagePtr_mutex)); pageRead(ret); /* release write lock on the page. */ writeunlock(ret->rwlatch); DEBUG("Loaded page %d => %x\n", pageid, (unsigned int) ret->memAddr); return ret; }