📄 pager.c
字号:
** If the number of references to the page drop to zero, then the
** page is added to the LRU list. When all references to all pages
** are released, a rollback occurs and the lock on the database is
** removed.
*/
int eDbpager_unref(void *pData){
PgHdr *pPg;
/* Decrement the reference count for this page
*/
pPg = DATA_TO_PGHDR(pData);
assert( pPg->nRef>0 );
pPg->nRef--;
REFINFO(pPg);
/* When the number of references to a page reach 0, call the
** destructor and add the page to the freelist.
*/
if( pPg->nRef==0 ){
Pager *pPager;
pPager = pPg->pPager;
pPg->pNextFree = 0;
pPg->pPrevFree = pPager->pLast;
pPager->pLast = pPg;
if( pPg->pPrevFree ){
pPg->pPrevFree->pNextFree = pPg;
}else{
pPager->pFirst = pPg;
}
if( pPg->needSync==0 && pPager->pFirstSynced==0 ){
pPager->pFirstSynced = pPg;
}
if( pPager->xDestructor ){
pPager->xDestructor(pData);
}
/* When all pages reach the freelist, drop the read lock from
** the database file.
*/
pPager->nRef--;
assert( pPager->nRef>=0 );
if( pPager->nRef==0 ){
pager_reset(pPager);
}
}
return eDb_OK;
}
/*
** Create a journal file for pPager. There should already be a write
** lock on the database file when this routine is called.
**
** Return eDb_OK if everything. Return an error code and release the
** write lock if anything goes wrong.
*/
static int pager_open_journal(Pager *pPager){
int rc;
assert( pPager->state==eDb_WRITELOCK );
assert( pPager->journalOpen==0 );
assert( pPager->useJournal );
eDbpager_pagecount(pPager);
pPager->aInJournal = eDbMalloc( pPager->dbSize/8 + 1 );
if( pPager->aInJournal==0 ){
eDbOsReadLock(&pPager->fd);
pPager->state = eDb_READLOCK;
return eDb_NOMEM;
}
rc = eDbOsOpenExclusive(pPager->zJournal, &pPager->jfd,pPager->tempFile);
if( rc!=eDb_OK ){
eDbFree(pPager->aInJournal);
pPager->aInJournal = 0;
eDbOsReadLock(&pPager->fd);
pPager->state = eDb_READLOCK;
return eDb_CANTOPEN;
}
pPager->journalOpen = 1;
pPager->journalStarted = 0;
pPager->needSync = 0;
pPager->alwaysRollback = 0;
pPager->nRec = 0;
if( pPager->errMask!=0 ){
rc = pager_errcode(pPager);
return rc;
}
pPager->origDbSize = pPager->dbSize;
if( journal_format==JOURNAL_FORMAT_3 ){
rc = eDbOsWrite(&pPager->jfd, aJournalMagic3, sizeof(aJournalMagic3));
if( rc==eDb_OK ){
rc = write32bits(&pPager->jfd, pPager->noSync ? 0xffffffff : 0);
}
if( rc==eDb_OK ){
eDbRandomness(sizeof(pPager->cksumInit), &pPager->cksumInit);
rc = write32bits(&pPager->jfd, pPager->cksumInit);
}
}else if( journal_format==JOURNAL_FORMAT_2 ){
rc = eDbOsWrite(&pPager->jfd, aJournalMagic2, sizeof(aJournalMagic2));
}else{
assert( journal_format==JOURNAL_FORMAT_1 );
rc = eDbOsWrite(&pPager->jfd, aJournalMagic1, sizeof(aJournalMagic1));
}
if( rc==eDb_OK ){
rc = write32bits(&pPager->jfd, pPager->dbSize);
}
if( pPager->ckptAutoopen && rc==eDb_OK ){
rc = eDbpager_ckpt_begin(pPager);
}
if( rc!=eDb_OK ){
rc = pager_unwritelock(pPager);
if( rc==eDb_OK ){
rc = eDb_FULL;
}
}
return rc;
}
/*
** Acquire a write-lock on the database. The lock is removed when
** the any of the following happen:
**
** * eDbpager_commit() is called.
** * eDbpager_rollback() is called.
** * eDbpager_close() is called.
** * eDbpager_unref() is called to on every outstanding page.
**
** The parameter to this routine is a pointer to any open page of the
** database file. Nothing changes about the page - it is used merely
** to acquire a pointer to the Pager structure and as proof that there
** is already a read-lock on the database.
**
** A journal file is opened if this is not a temporary file. For
** temporary files, the opening of the journal file is deferred until
** there is an actual need to write to the journal.
**
** If the database is already write-locked, this routine is a no-op.
*/
int eDbpager_begin(void *pData){
PgHdr *pPg = DATA_TO_PGHDR(pData);
Pager *pPager = pPg->pPager;
int rc = eDb_OK;
assert( pPg->nRef>0 );
assert( pPager->state!=eDb_UNLOCK );
if( pPager->state==eDb_READLOCK ){
assert( pPager->aInJournal==0 );
rc = eDbOsWriteLock(&pPager->fd);
if( rc!=eDb_OK ){
return rc;
}
pPager->state = eDb_WRITELOCK;
pPager->dirtyFile = 0;
TRACE1("TRANSACTION\n");
if( pPager->useJournal && !pPager->tempFile ){
rc = pager_open_journal(pPager);
}
}
return rc;
}
/*
** Mark a data page as writeable. The page is written into the journal
** if it is not there already. This routine must be called before making
** changes to a page.
**
** The first time this routine is called, the pager creates a new
** journal and acquires a write lock on the database. If the write
** lock could not be acquired, this routine returns eDb_BUSY. The
** calling routine must check for that return value and be careful not to
** change any page data until this routine returns eDb_OK.
**
** If the journal file could not be written because the disk is full,
** then this routine returns eDb_FULL and does an immediate rollback.
** All subsequent write attempts also return eDb_FULL until there
** is a call to eDbpager_commit() or eDbpager_rollback() to
** reset.
*/
int eDbpager_write(void *pData){
PgHdr *pPg = DATA_TO_PGHDR(pData);
Pager *pPager = pPg->pPager;
int rc = eDb_OK;
/* Check for errors
*/
if( pPager->errMask ){
return pager_errcode(pPager);
}
if( pPager->readOnly ){
return eDb_PERM;
}
/* Mark the page as dirty. If the page has already been written
** to the journal then we can return right away.
*/
pPg->dirty = 1;
if( pPg->inJournal && (pPg->inCkpt || pPager->ckptInUse==0)){
pPager->dirtyFile = 1;
return eDb_OK;
}
/* If we get this far, it means that the page needs to be
** written to the transaction journal or the ckeckpoint journal
** or both.
**
** First check to see that the transaction journal exists and
** create it if it does not.
*/
assert( pPager->state!=eDb_UNLOCK );
rc = eDbpager_begin(pData);
if( rc!=eDb_OK ){
return rc;
}
assert( pPager->state==eDb_WRITELOCK );
if( !pPager->journalOpen && pPager->useJournal ){
rc = pager_open_journal(pPager);
if( rc!=eDb_OK ) return rc;
}
assert( pPager->journalOpen || !pPager->useJournal );
pPager->dirtyFile = 1;
/* The transaction journal now exists and we have a write lock on the
** main database file. Write the current page to the transaction
** journal if it is not there already.
*/
if(!pPg->inJournal && pPager->useJournal){
if((int)pPg->pgno <= pPager->origDbSize){
int szPg;
u32 saved;
if( journal_format>=JOURNAL_FORMAT_3 ){
u32 cksum = pager_cksum(pPager, pPg->pgno, pData);
saved = *(u32*)PGHDR_TO_EXTRA(pPg);
store32bits(cksum, pPg, eDb_PAGE_SIZE);
szPg = eDb_PAGE_SIZE+8;
}else{
szPg = eDb_PAGE_SIZE+4;
}
store32bits(pPg->pgno, pPg, -4);
CODEC(pPager, pData, pPg->pgno, 7);
rc = eDbOsWrite(&pPager->jfd, &((char*)pData)[-4], szPg);
TRACE3("JOURNAL %d %d\n", pPg->pgno, pPg->needSync);
CODEC(pPager, pData, pPg->pgno, 0);
if( journal_format>=JOURNAL_FORMAT_3 ){
*(u32*)PGHDR_TO_EXTRA(pPg) = saved;
}
if( rc!=eDb_OK ){
eDbpager_rollback(pPager);
pPager->errMask |= PAGER_ERR_FULL;
return rc;
}
pPager->nRec++;
assert( pPager->aInJournal!=0 );
pPager->aInJournal[pPg->pgno/8] |= 1<<(pPg->pgno&7);
pPg->needSync = !pPager->noSync;
pPg->inJournal = 1;
if( pPager->ckptInUse ){
pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
page_add_to_ckpt_list(pPg);
}
}/* end if((int)pPg->pgno <= pPager->origDbSize)*/
else{
pPg->needSync = !pPager->journalStarted && !pPager->noSync;
TRACE3("APPEND %d %d\n", pPg->pgno, pPg->needSync);
}
if( pPg->needSync ){
pPager->needSync = 1;
}
}/*end if(!pPg->inJournal && pPager->useJournal)*/
/* If the checkpoint journal is open and the page is not in it,
** then write the current page to the checkpoint journal. Note that
** the checkpoint journal always uses the simplier format 2 that lacks
** checksums. The header is also omitted from the checkpoint journal.
*/
if( pPager->ckptInUse && !pPg->inCkpt && (int)pPg->pgno<=pPager->ckptSize ){
assert( pPg->inJournal || (int)pPg->pgno>pPager->origDbSize );
store32bits(pPg->pgno, pPg, -4);
CODEC(pPager, pData, pPg->pgno, 7);
rc = eDbOsWrite(&pPager->cpfd, &((char*)pData)[-4], eDb_PAGE_SIZE+4);
TRACE2("CKPT-JOURNAL %d\n", pPg->pgno);
CODEC(pPager, pData, pPg->pgno, 0);
if( rc!=eDb_OK ){
eDbpager_rollback(pPager);
pPager->errMask |= PAGER_ERR_FULL;
return rc;
}
pPager->ckptNRec++;
assert( pPager->aInCkpt!=0 );
pPager->aInCkpt[pPg->pgno/8] |= 1<<(pPg->pgno&7);
page_add_to_ckpt_list(pPg);
}
/* Update the database size and return.
*/
if( pPager->dbSize<(int)pPg->pgno ){
pPager->dbSize = pPg->pgno;
}
return rc;
}
/*
** Return TRUE if the page given in the argument was previously passed
** to eDbpager_write(). In other words, return TRUE if it is ok
** to change the content of the page.
*/
int eDbpager_iswriteable(void *pData){
PgHdr *pPg = DATA_TO_PGHDR(pData);
return pPg->dirty;
}
/*
** Replace the content of a single page with the information in the third
** argument.
*/
int eDbpager_overwrite(Pager *pPager, Pgno pgno, void *pData){
void *pPage;
int rc;
rc = eDbpager_get(pPager, pgno, &pPage);
if( rc==eDb_OK ){
rc = eDbpager_write(pPage);
if( rc==eDb_OK ){
memcpy(pPage, pData, eDb_PAGE_SIZE);
}
eDbpager_unref(pPage);
}
return rc;
}
/*
** A call to this routine tells the pager that it is not necessary to
** write the information on page "pgno" back to the disk, even though
** that page might be marked as dirty.
**
** The overlying software layer calls this routine when all of the data
** on the given page is unused. The pager marks the page as clean so
** that it does not get written to disk.
**
** Tests show that this optimization, together with the
** eDbpager_dont_rollback() below, more than double the speed
** of large INSERT operations and quadruple the speed of large DELETEs.
**
** When this routine is called, set the alwaysRollback flag to true.
** Subsequent calls to eDbpager_dont_rollback() for the same page
** will thereafter be ignored. This is necessary to avoid a problem
** where a page with data is added to the freelist during one part of
** a transaction then removed from the freelist during a later part
** of the same transaction and reused for some other purpose. When it
** is first added to the freelist, this routine is called. When reused,
** the dont_rollback() routine is called. But because the page contains
** critical data, we still need to be sure it gets rolled back in spite
** of the dont_rollback() call.
*/
void eDbpager_dont_write(Pager *pPager, Pgno pgno){
PgHdr *pPg;
pPg = pager_lookup(pPager, pgno);
pPg->alwaysRollback = 1;
if( pPg && pPg->dirty ){
if( pPager->dbSize==(int)pPg->pgno && pPager->origDbSize<pPager->dbSize ){
/* If this pages is the last page in the file and the file has grown
** during the current transaction, then do NOT mark the page as clean.
** When the database file grows, we must make sure that the last page
** gets written at least once so that the disk file will be the correct
** size. If you do not write this page and the size of the file
** on the disk ends up being too small, that can lead to database
** corruption during the next transaction.
*/
}else{
TRACE2("DONT_WRITE %d\n", pgno);
pPg->dirty = 0;
}
}
}
/*
** A call to this routine tells the pager that if a rollback occurs,
** it is not necessary to restore the data on the given page. This
** means that the pager does not have to record the given page in the
** rollback journal.
*/
void eDbpager_dont_rollback(void *pData){
PgHdr *pPg = DATA_TO_PGHDR(pData);
Pager *pPager = pPg->pPager;
if( pPager->state!=eDb_WRITELOCK || pPager->journalOpen==0 ) return;
if( pPg->alwaysRollback || pPager->alwaysRollback ) return;
if( !pPg->inJournal && (int)pPg->pgno <= pPager->origDbSize ){
assert( pPager->aInJournal!=0 );
pPager->aInJournal[pPg->pgno/8] |= 1<<(pPg->pgno&7);
pPg->inJournal = 1;
if( pPager->ckptInUse ){
pPag
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -