📄 pager.c
字号:
*/
off_t hdrSz, pgSz, jSz;
hdrSz = JOURNAL_HDR_SZ(journal_format);
pgSz = JOURNAL_PG_SZ(journal_format);
rc = eDbOsFileSize(&pPager->jfd, &jSz);
if( rc!=0 ) return rc;
assert( pPager->nRec*pgSz+hdrSz==jSz );
}
#endif
if( journal_format>=3 ){
/* Write the nRec value into the journal file header */
off_t szJ;
if( pPager->fullSync ){
TRACE1("SYNC\n");
rc = eDbOsSync(&pPager->jfd);
if( rc!=0 ) return rc;
}
eDbOsSeek(&pPager->jfd, sizeof(aJournalMagic1));
rc = write32bits(&pPager->jfd, pPager->nRec);
if( rc ) return rc;
szJ = JOURNAL_HDR_SZ(journal_format) +
pPager->nRec*JOURNAL_PG_SZ(journal_format);
eDbOsSeek(&pPager->jfd, szJ);
}
TRACE1("SYNC\n");
rc = eDbOsSync(&pPager->jfd);
if( rc!=0 ) return rc;
pPager->journalStarted = 1;
}
pPager->needSync = 0;
/* Erase the needSync flag from every page.
*/
for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
pPg->needSync = 0;
}
pPager->pFirstSynced = pPager->pFirst;
}
#ifndef NDEBUG
/* If the Pager.needSync flag is clear then the PgHdr.needSync
** flag must also be clear for all pages. Verify that this
** invariant is true.
*/
else{
for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){
assert( pPg->needSync==0 );
}
assert( pPager->pFirstSynced==pPager->pFirst );
}
#endif
return rc;
}
/*
** Given a list of pages (connected by the PgHdr.pDirty pointer) write
** every one of those pages out to the database file and mark them all
** as clean.
*/
static int pager_write_pagelist(PgHdr *pList){
Pager *pPager;
int rc;
if( pList==0 ) return eDb_OK;
pPager = pList->pPager;
while( pList ){
assert( pList->dirty );
eDbOsSeek(&pPager->fd, (pList->pgno-1)*(off_t)eDb_PAGE_SIZE);
CODEC(pPager, PGHDR_TO_DATA(pList), pList->pgno, 6);
TRACE2("STORE %d\n", pList->pgno);
rc = eDbOsWrite(&pPager->fd, PGHDR_TO_DATA(pList), eDb_PAGE_SIZE);
CODEC(pPager, PGHDR_TO_DATA(pList), pList->pgno, 0);
if( rc ) return rc;
pList->dirty = 0;
pList = pList->pDirty;
}
return eDb_OK;
}
/*
** Collect every dirty page into a dirty list and
** return a pointer to the head of that list. All pages are
** collected even if they are still in use.
*/
static PgHdr *pager_get_all_dirty_pages(Pager *pPager){
PgHdr *p, *pList;
pList = 0;
for(p=pPager->pAll; p; p=p->pNextAll){
if( p->dirty ){
p->pDirty = pList;
pList = p;
}
}
return pList;
}
/*
** Acquire a page.
**
** A read lock on the disk file is obtained when the first page is acquired.
** This read lock is dropped when the last page is released.
**
** A _get works for any page number greater than 0. If the database
** file is smaller than the requested page, then no actual disk
** read occurs and the memory image of the page is initialized to
** all zeros. The extra data appended to a page is always initialized
** to zeros the first time a page is loaded into memory.
**
** The acquisition might fail for several reasons. In all cases,
** an appropriate error code is returned and *ppPage is set to NULL.
**
** See also eDbpager_lookup(). Both this routine and _lookup() attempt
** to find a page in the in-memory cache first. If the page is not already
** in memory, this routine goes to disk to read it in whereas _lookup()
** just returns 0. This routine acquires a read-lock the first time it
** has to go to disk, and could also playback an old journal if necessary.
** Since _lookup() never goes to disk, it never has to deal with locks
** or journal files.
*/
int eDbpager_get(Pager *pPager, Pgno pgno, void **ppPage){
PgHdr *pPg;
int rc;
/* Make sure we have not hit any critical errors.
*/
assert( pPager!=0 );
assert( pgno!=0 );
*ppPage = 0;
if( pPager->errMask & ~(PAGER_ERR_FULL) ){
return pager_errcode(pPager);
}
/* If this is the first page accessed, then get a read lock
** on the database file.
*/
if( pPager->nRef==0 ){
rc = eDbOsReadLock(&pPager->fd);
if( rc!=eDb_OK ){
return rc;
}
pPager->state = eDb_READLOCK;
/* If a journal file exists, try to play it back.
*/
if(pPager->useJournal && eDbOsFileExists(pPager->zJournal)){
int rc;
/* Get a write lock on the database
*/
rc = eDbOsWriteLock(&pPager->fd);
if( rc!=eDb_OK ){
if( eDbOsUnlock(&pPager->fd)!=eDb_OK ){
/* This should never happen! */
rc = eDb_INTERNAL;
}
return rc;
}
pPager->state = eDb_WRITELOCK;
/* Open the journal for reading only. Return eDb_BUSY if
** we are unable to open the journal file.
**
** The journal file does not need to be locked itself. The
** journal file is never open unless the main database file holds
** a write lock, so there is never any chance of two or more
** processes opening the journal at the same time.
*/
rc = eDbOsOpenReadOnly(pPager->zJournal, &pPager->jfd);
if( rc!=eDb_OK ){
rc = eDbOsUnlock(&pPager->fd);
assert( rc==eDb_OK );
return eDb_BUSY;
}
pPager->journalOpen = 1;
pPager->journalStarted = 0;
/* Playback and delete the journal. Drop the database write
** lock and reacquire the read lock.
*/
rc = pager_playback(pPager, 0);
if( rc!=eDb_OK ){
return rc;
}
}/*if(pPager->useJournal && eDbOsFileExists(pPager->zJournal))*/
pPg = 0;
} /*if( pPager->nRef==0 )*/
else{
/* Search for page in cache */
pPg = pager_lookup(pPager, pgno);
}
if( pPg==0 ){
/* The requested page is not in the page cache. */
int h;
pPager->nMiss++;
if( pPager->nPage<pPager->mxPage || pPager->pFirst==0 ){
/* Create a new page */
pPg = eDbMallocRaw( sizeof(*pPg) + eDb_PAGE_SIZE
+ sizeof(u32) + pPager->nExtra );
if( pPg==0 ){
pager_unwritelock(pPager);
pPager->errMask |= PAGER_ERR_MEM;
return eDb_NOMEM;
}
memset(pPg, 0, sizeof(*pPg));
pPg->pPager = pPager;
pPg->pNextAll = pPager->pAll;
if( pPager->pAll ){
pPager->pAll->pPrevAll = pPg;
}
pPg->pPrevAll = 0;
pPager->pAll = pPg;
pPager->nPage++;
}/* end if(pPager->nPage<pPager->mxPage || pPager->pFirst==0)*/
else{
/* Find a page to recycle. Try to locate a page that does not
** require us to do an fsync() on the journal.
*/
pPg = pPager->pFirstSynced;
/* If we could not find a page that does not require an fsync()
** on the journal file then fsync the journal file. This is a
** very slow operation, so we work hard to avoid it. But sometimes
** it can't be helped.
*/
if( pPg==0 ){
int rc = syncJournal(pPager);
if( rc!=0 ){
eDbpager_rollback(pPager);
return eDb_IOERR;
}
pPg = pPager->pFirst;
}
assert( pPg->nRef==0 );
/* Write the page to the database file if it is dirty.
*/
if( pPg->dirty ){
assert( pPg->needSync==0 );
pPg->pDirty = 0;
rc = pager_write_pagelist( pPg );
if( rc!=eDb_OK ){
eDbpager_rollback(pPager);
return eDb_IOERR;
}
}
assert( pPg->dirty==0 );
/* If the page we are recycling is marked as alwaysRollback, then
** set the global alwaysRollback flag, thus disabling the
** eDb_dont_rollback() optimization for the rest of this transaction.
** It is necessary to do this because the page marked alwaysRollback
** might be reloaded at a later time but at that point we won't remember
** that is was marked alwaysRollback. This means that all pages must
** be marked as alwaysRollback from here on out.
*/
if( pPg->alwaysRollback ){
pPager->alwaysRollback = 1;
}
/* Unlink the old page from the free list and the hash table
*/
if( pPg==pPager->pFirstSynced ){
PgHdr *p = pPg->pNextFree;
while( p && p->needSync ){ p = p->pNextFree; }
pPager->pFirstSynced = p;
}
if( pPg->pPrevFree ){
pPg->pPrevFree->pNextFree = pPg->pNextFree;
}else{
assert( pPager->pFirst==pPg );
pPager->pFirst = pPg->pNextFree;
}
if( pPg->pNextFree ){
pPg->pNextFree->pPrevFree = pPg->pPrevFree;
}else{
assert( pPager->pLast==pPg );
pPager->pLast = pPg->pPrevFree;
}
pPg->pNextFree = pPg->pPrevFree = 0;
if( pPg->pNextHash ){
pPg->pNextHash->pPrevHash = pPg->pPrevHash;
}
if( pPg->pPrevHash ){
pPg->pPrevHash->pNextHash = pPg->pNextHash;
}else{
h = pager_hash(pPg->pgno);
assert( pPager->aHash[h]==pPg );
pPager->aHash[h] = pPg->pNextHash;
}
pPg->pNextHash = pPg->pPrevHash = 0;
pPager->nOvfl++;
}/* end end if(pPager->nPage<pPager->mxPage || pPager->pFirst==0) else */
pPg->pgno = pgno;
if( pPager->aInJournal && (int)pgno<=pPager->origDbSize ){
eDbCheckMemory(pPager->aInJournal, pgno/8);
assert( pPager->journalOpen );
pPg->inJournal = (pPager->aInJournal[pgno/8] & (1<<(pgno&7)))!=0;
pPg->needSync = 0;
}else{
pPg->inJournal = 0;
pPg->needSync = 0;
}
if( pPager->aInCkpt && (int)pgno<=pPager->ckptSize
&& (pPager->aInCkpt[pgno/8] & (1<<(pgno&7)))!=0 ){
page_add_to_ckpt_list(pPg);
}else{
page_remove_from_ckpt_list(pPg);
}
pPg->dirty = 0;
pPg->nRef = 1;
REFINFO(pPg);
pPager->nRef++;
h = pager_hash(pgno);
pPg->pNextHash = pPager->aHash[h];
pPager->aHash[h] = pPg;
if( pPg->pNextHash ){
assert( pPg->pNextHash->pPrevHash==0 );
pPg->pNextHash->pPrevHash = pPg;
}
if( pPager->nExtra>0 ){
memset(PGHDR_TO_EXTRA(pPg), 0, pPager->nExtra);
}
if( pPager->dbSize<0 ) eDbpager_pagecount(pPager);
if( pPager->errMask!=0 ){
eDbpager_unref(PGHDR_TO_DATA(pPg));
rc = pager_errcode(pPager);
return rc;
}
if( pPager->dbSize<(int)pgno ){
memset(PGHDR_TO_DATA(pPg), 0, eDb_PAGE_SIZE);
}else{
int rc;
eDbOsSeek(&pPager->fd, (pgno-1)*(off_t)eDb_PAGE_SIZE);
rc = eDbOsRead(&pPager->fd, PGHDR_TO_DATA(pPg), eDb_PAGE_SIZE);
TRACE2("FETCH %d\n", pPg->pgno);
CODEC(pPager, PGHDR_TO_DATA(pPg), pPg->pgno, 3);
if( rc!=eDb_OK ){
off_t fileSize;
if( eDbOsFileSize(&pPager->fd,&fileSize)!=eDb_OK
|| fileSize>=pgno*eDb_PAGE_SIZE ){
eDbpager_unref(PGHDR_TO_DATA(pPg));
return rc;
}else{
memset(PGHDR_TO_DATA(pPg), 0, eDb_PAGE_SIZE);
}
}
}
}/*end if(pPg == 0)*/
else{
/* The requested page is in the page cache. */
pPager->nHit++;
page_ref(pPg);
}
*ppPage = PGHDR_TO_DATA(pPg);
return eDb_OK;
}
/*
** Acquire a page if it is already in the in-memory cache. Do
** not read the page from disk. Return a pointer to the page,
** or 0 if the page is not in cache.
**
** See also eDbpager_get(). The difference between this routine
** and eDbpager_get() is that _get() will go to the disk and read
** in the page if the page is not already in cache. This routine
** returns NULL if the page is not in cache or if a disk I/O error
** has ever happened.
*/
void *eDbpager_lookup(Pager *pPager, Pgno pgno){
PgHdr *pPg;
assert( pPager!=0 );
assert( pgno!=0 );
if( pPager->errMask & ~(PAGER_ERR_FULL) ){
return 0;
}
/* if( pPager->nRef==0 ){
** return 0;
** }
*/
pPg = pager_lookup(pPager, pgno);
if( pPg==0 ) return 0;
page_ref(pPg);
return PGHDR_TO_DATA(pPg);
}
/*
** Release a page.
**
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -