aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/pager.c132
-rw-r--r--src/pcache.c26
-rw-r--r--src/pcache.h4
-rw-r--r--src/test3.c6
4 files changed, 119 insertions, 49 deletions
diff --git a/src/pager.c b/src/pager.c
index 201580808..d24337cd5 100644
--- a/src/pager.c
+++ b/src/pager.c
@@ -872,6 +872,7 @@ static int assert_pager_state(Pager *p){
** state.
*/
if( MEMDB ){
+ assert( !isOpen(p->fd) );
assert( p->noSync );
assert( p->journalMode==PAGER_JOURNALMODE_OFF
|| p->journalMode==PAGER_JOURNALMODE_MEMORY
@@ -958,7 +959,7 @@ static int assert_pager_state(Pager *p){
** back to OPEN state.
*/
assert( pPager->errCode!=SQLITE_OK );
- assert( sqlite3PcacheRefCount(pPager->pPCache)>0 );
+ assert( sqlite3PcacheRefCount(pPager->pPCache)>0 || pPager->tempFile );
break;
}
@@ -1170,6 +1171,8 @@ static int jrnlBufferSize(Pager *pPager){
return JOURNAL_HDR_SZ(pPager) + JOURNAL_PG_SZ(pPager);
}
+#else
+# define jrnlBufferSize(x) 0
#endif
/*
@@ -1818,13 +1821,17 @@ static void pager_unlock(Pager *pPager){
** it can safely move back to PAGER_OPEN state. This happens in both
** normal and exclusive-locking mode.
*/
+ assert( pPager->errCode==SQLITE_OK || !MEMDB );
if( pPager->errCode ){
- assert( !MEMDB );
- pager_reset(pPager);
- pPager->changeCountDone = pPager->tempFile;
- pPager->eState = PAGER_OPEN;
- pPager->errCode = SQLITE_OK;
+ if( pPager->tempFile==0 ){
+ pager_reset(pPager);
+ pPager->changeCountDone = 0;
+ pPager->eState = PAGER_OPEN;
+ }else{
+ pPager->eState = (isOpen(pPager->jfd) ? PAGER_OPEN : PAGER_READER);
+ }
if( USEFETCH(pPager) ) sqlite3OsUnfetch(pPager->fd, 0, 0);
+ pPager->errCode = SQLITE_OK;
}
pPager->journalOff = 0;
@@ -1869,6 +1876,25 @@ static int pager_error(Pager *pPager, int rc){
static int pager_truncate(Pager *pPager, Pgno nPage);
/*
+** The write transaction open on the pager passed as the only argument is
+** being committed. This function returns true if all dirty pages should
+** be flushed to disk, or false otherwise. Pages should be flushed to disk
+** unless one of the following is true:
+**
+** * The db is an in-memory database.
+**
+** * The db is a temporary database and the db file has not been opened.
+**
+** * The db is a temporary database and the cache contains less than
+** C/4 dirty pages, where C is the configured cache-size.
+*/
+static int pagerFlushOnCommit(Pager *pPager){
+ if( pPager->tempFile==0 ) return 1;
+ if( !isOpen(pPager->fd) ) return 0;
+ return (sqlite3PCachePercentDirty(pPager->pPCache)>=25);
+}
+
+/*
** This routine ends a transaction. A transaction is usually ended by
** either a COMMIT or a ROLLBACK operation. This routine may be called
** after rollback of a hot-journal, or if an error occurs while opening
@@ -2006,7 +2032,11 @@ static int pager_end_transaction(Pager *pPager, int hasMaster, int bCommit){
sqlite3BitvecDestroy(pPager->pInJournal);
pPager->pInJournal = 0;
pPager->nRec = 0;
- sqlite3PcacheCleanAll(pPager->pPCache);
+ if( MEMDB || pagerFlushOnCommit(pPager) ){
+ sqlite3PcacheCleanAll(pPager->pPCache);
+ }else{
+ sqlite3PcacheClearWritable(pPager->pPCache);
+ }
sqlite3PcacheTruncate(pPager->pPCache, pPager->dbSize);
if( pagerUseWal(pPager) ){
@@ -2291,7 +2321,7 @@ static int pager_playback_one_page(
pPg = sqlite3PagerLookup(pPager, pgno);
}
assert( pPg || !MEMDB );
- assert( pPager->eState!=PAGER_OPEN || pPg==0 );
+ assert( pPager->eState!=PAGER_OPEN || pPg==0 || pPager->tempFile );
PAGERTRACE(("PLAYBACK %d page %d hash(%08x) %s\n",
PAGERID(pPager), pgno, pager_datahash(pPager->pageSize, (u8*)aData),
(isMainJrnl?"main-journal":"sub-journal")
@@ -2374,9 +2404,13 @@ static int pager_playback_one_page(
** be written out into the database file before its journal file
** segment is synced. If a crash occurs during or following this,
** database corruption may ensue.
+ **
+ ** Update: Another exception is for temp files that are not
+ ** in-memory databases. In this case the page may have been dirty
+ ** at the start of the transaction.
*/
assert( !pagerUseWal(pPager) );
- sqlite3PcacheMakeClean(pPg);
+ if( pPager->tempFile==0 ) sqlite3PcacheMakeClean(pPg);
}
pager_set_pagehash(pPg);
@@ -3168,6 +3202,8 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){
*/
assert( pPager->eState==PAGER_OPEN );
assert( pPager->eLock>=SHARED_LOCK );
+ assert( isOpen(pPager->fd) );
+ assert( pPager->tempFile==0 );
nPage = sqlite3WalDbsize(pPager->pWal);
/* If the number of pages in the database is not available from the
@@ -3175,14 +3211,11 @@ static int pagerPagecount(Pager *pPager, Pgno *pnPage){
** the database file. If the size of the database file is not an
** integer multiple of the page-size, round up the result.
*/
- if( nPage==0 ){
+ if( nPage==0 && ALWAYS(isOpen(pPager->fd)) ){
i64 n = 0; /* Size of db file in bytes */
- assert( isOpen(pPager->fd) || pPager->tempFile );
- if( isOpen(pPager->fd) ){
- int rc = sqlite3OsFileSize(pPager->fd, &n);
- if( rc!=SQLITE_OK ){
- return rc;
- }
+ int rc = sqlite3OsFileSize(pPager->fd, &n);
+ if( rc!=SQLITE_OK ){
+ return rc;
}
nPage = (Pgno)((n+pPager->pageSize-1) / pPager->pageSize);
}
@@ -4258,8 +4291,9 @@ static int pager_write_pagelist(Pager *pPager, PgHdr *pList){
/* This function is only called for rollback pagers in WRITER_DBMOD state. */
assert( !pagerUseWal(pPager) );
- assert( pPager->eState==PAGER_WRITER_DBMOD );
+ assert( pPager->tempFile || pPager->eState==PAGER_WRITER_DBMOD );
assert( pPager->eLock==EXCLUSIVE_LOCK );
+ assert( isOpen(pPager->fd) || pList->pDirty==0 );
/* If the file is a temp-file has not yet been opened, open it now. It
** is not possible for rc to be other than SQLITE_OK if this branch
@@ -4927,6 +4961,7 @@ static int hasHotJournal(Pager *pPager, int *pExists){
if( rc==SQLITE_OK && !locked ){
Pgno nPage; /* Number of pages in database file */
+ assert( pPager->tempFile==0 );
rc = pagerPagecount(pPager, &nPage);
if( rc==SQLITE_OK ){
/* If the database is zero pages in size, that means that either (1) the
@@ -5019,17 +5054,17 @@ int sqlite3PagerSharedLock(Pager *pPager){
/* This routine is only called from b-tree and only when there are no
** outstanding pages. This implies that the pager state should either
** be OPEN or READER. READER is only possible if the pager is or was in
- ** exclusive access mode.
- */
+ ** exclusive access mode. */
assert( sqlite3PcacheRefCount(pPager->pPCache)==0 );
assert( assert_pager_state(pPager) );
assert( pPager->eState==PAGER_OPEN || pPager->eState==PAGER_READER );
- if( NEVER(MEMDB && pPager->errCode) ){ return pPager->errCode; }
+ assert( pPager->errCode==SQLITE_OK );
if( !pagerUseWal(pPager) && pPager->eState==PAGER_OPEN ){
int bHotJournal = 1; /* True if there exists a hot journal-file */
assert( !MEMDB );
+ assert( pPager->tempFile==0 || pPager->eLock==EXCLUSIVE_LOCK );
rc = pager_wait_on_lock(pPager, SHARED_LOCK);
if( rc!=SQLITE_OK ){
@@ -5115,7 +5150,7 @@ int sqlite3PagerSharedLock(Pager *pPager){
assert( rc==SQLITE_OK );
rc = pagerSyncHotJournal(pPager);
if( rc==SQLITE_OK ){
- rc = pager_playback(pPager, 1);
+ rc = pager_playback(pPager, !pPager->tempFile);
pPager->eState = PAGER_OPEN;
}
}else if( !pPager->exclusiveMode ){
@@ -5211,7 +5246,7 @@ int sqlite3PagerSharedLock(Pager *pPager){
rc = pagerBeginReadTransaction(pPager);
}
- if( pPager->eState==PAGER_OPEN && rc==SQLITE_OK ){
+ if( pPager->tempFile==0 && pPager->eState==PAGER_OPEN && rc==SQLITE_OK ){
rc = pagerPagecount(pPager, &pPager->dbSize);
}
@@ -5344,7 +5379,7 @@ int sqlite3PagerGet(
);
if( rc==SQLITE_OK && pData ){
- if( pPager->eState>PAGER_READER ){
+ if( pPager->eState>PAGER_READER || pPager->tempFile ){
pPg = sqlite3PagerLookup(pPager, pgno);
}
if( pPg==0 ){
@@ -5411,7 +5446,8 @@ int sqlite3PagerGet(
goto pager_acquire_err;
}
- if( MEMDB || pPager->dbSize<pgno || noContent || !isOpen(pPager->fd) ){
+ assert( !isOpen(pPager->fd) || !MEMDB );
+ if( !isOpen(pPager->fd) || pPager->dbSize<pgno || noContent ){
if( pgno>pPager->mxPgno ){
rc = SQLITE_FULL;
goto pager_acquire_err;
@@ -5553,24 +5589,24 @@ static int pager_open_journal(Pager *pPager){
if( pPager->journalMode==PAGER_JOURNALMODE_MEMORY ){
sqlite3MemJournalOpen(pPager->jfd);
}else{
- const int flags = /* VFS flags to open journal file */
- SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE|
- (pPager->tempFile ?
- (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL):
- (SQLITE_OPEN_MAIN_JOURNAL)
- );
+ int flags = SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE;
+ int nSpill;
+ if( pPager->tempFile ){
+ flags |= (SQLITE_OPEN_DELETEONCLOSE|SQLITE_OPEN_TEMP_JOURNAL);
+ nSpill = sqlite3Config.nStmtSpill;
+ }else{
+ flags |= SQLITE_OPEN_MAIN_JOURNAL;
+ nSpill = jrnlBufferSize(pPager);
+ }
+
/* Verify that the database still has the same name as it did when
** it was originally opened. */
rc = databaseIsUnmoved(pPager);
if( rc==SQLITE_OK ){
-#ifdef SQLITE_ENABLE_ATOMIC_WRITE
- rc = sqlite3JournalOpen(
- pVfs, pPager->zJournal, pPager->jfd, flags, jrnlBufferSize(pPager)
+ rc = sqlite3JournalOpen (
+ pVfs, pPager->zJournal, pPager->jfd, flags, nSpill
);
-#else
- rc = sqlite3OsOpen(pVfs, pPager->zJournal, pPager->jfd, flags, 0);
-#endif
}
}
assert( rc!=SQLITE_OK || isOpen(pPager->jfd) );
@@ -5941,6 +5977,7 @@ int sqlite3PagerWrite(PgHdr *pPg){
if( pPager->nSavepoint ) return subjournalPageIfRequired(pPg);
return SQLITE_OK;
}else if( pPager->sectorSize > (u32)pPager->pageSize ){
+ assert( pPager->tempFile==0 );
return pagerWriteLargeSector(pPg);
}else{
return pager_write(pPg);
@@ -6173,17 +6210,21 @@ int sqlite3PagerCommitPhaseOne(
/* If a prior error occurred, report that error again. */
if( NEVER(pPager->errCode) ) return pPager->errCode;
+ /* Provide the ability to easily simulate an I/O error during testing */
+ if( (rc = sqlite3FaultSim(400))!=SQLITE_OK ) return rc;
+
PAGERTRACE(("DATABASE SYNC: File=%s zMaster=%s nSize=%d\n",
pPager->zFilename, zMaster, pPager->dbSize));
/* If no database changes have been made, return early. */
if( pPager->eState<PAGER_WRITER_CACHEMOD ) return SQLITE_OK;
- if( MEMDB ){
+ assert( MEMDB==0 || pPager->tempFile );
+ assert( isOpen(pPager->fd) || pPager->tempFile );
+ if( 0==pagerFlushOnCommit(pPager) ){
/* If this is an in-memory db, or no pages have been written to, or this
** function has already been called, it is mostly a no-op. However, any
- ** backup in progress needs to be restarted.
- */
+ ** backup in progress needs to be restarted. */
sqlite3BackupRestart(pPager->pBackup);
}else{
if( pagerUseWal(pPager) ){
@@ -6522,10 +6563,10 @@ void sqlite3PagerCacheStat(Pager *pPager, int eStat, int reset, int *pnVal){
}
/*
-** Return true if this is an in-memory pager.
+** Return true if this is an in-memory or temp-file backed pager.
*/
int sqlite3PagerIsMemdb(Pager *pPager){
- return MEMDB;
+ return pPager->tempFile;
}
/*
@@ -6805,7 +6846,7 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){
/* In order to be able to rollback, an in-memory database must journal
** the page we are moving from.
*/
- if( MEMDB ){
+ if( pPager->tempFile ){
rc = sqlite3PagerWrite(pPg);
if( rc ) return rc;
}
@@ -6862,7 +6903,7 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){
assert( !pPgOld || pPgOld->nRef==1 );
if( pPgOld ){
pPg->flags |= (pPgOld->flags&PGHDR_NEED_SYNC);
- if( MEMDB ){
+ if( pPager->tempFile ){
/* Do not discard pages from an in-memory database since we might
** need to rollback later. Just move the page out of the way. */
sqlite3PcacheMove(pPgOld, pPager->dbSize+1);
@@ -6879,7 +6920,7 @@ int sqlite3PagerMovepage(Pager *pPager, DbPage *pPg, Pgno pgno, int isCommit){
** to exist, in case the transaction needs to roll back. Use pPgOld
** as the original page since it has already been allocated.
*/
- if( MEMDB ){
+ if( pPager->tempFile ){
assert( pPgOld );
sqlite3PcacheMove(pPgOld, origPgno);
sqlite3PagerUnrefNotNull(pPgOld);
@@ -7132,7 +7173,8 @@ sqlite3_backup **sqlite3PagerBackupPtr(Pager *pPager){
** Unless this is an in-memory or temporary database, clear the pager cache.
*/
void sqlite3PagerClearCache(Pager *pPager){
- if( !MEMDB && pPager->tempFile==0 ) pager_reset(pPager);
+ assert( MEMDB==0 || pPager->tempFile );
+ if( pPager->tempFile==0 ) pager_reset(pPager);
}
#endif
diff --git a/src/pcache.c b/src/pcache.c
index 61e758742..f700c2ff6 100644
--- a/src/pcache.c
+++ b/src/pcache.c
@@ -254,7 +254,7 @@ sqlite3_pcache_page *sqlite3PcacheFetch(
/*
** If the sqlite3PcacheFetch() routine is unable to allocate a new
-** page because new clean pages are available for reuse and the cache
+** page because no clean pages are available for reuse and the cache
** size limit has been reached, then this routine can be invoked to
** try harder to allocate a page. This routine might invoke the stress
** callback to spill dirty pages to the journal. It will then try to
@@ -440,6 +440,17 @@ void sqlite3PcacheCleanAll(PCache *pCache){
}
/*
+** Clear the PGHDR_NEED_SYNC and PGHDR_WRITEABLE flag from all dirty pages.
+*/
+void sqlite3PcacheClearWritable(PCache *pCache){
+ PgHdr *p;
+ for(p=pCache->pDirty; p; p=p->pDirtyNext){
+ p->flags &= ~(PGHDR_NEED_SYNC|PGHDR_WRITEABLE);
+ }
+ pCache->pSynced = pCache->pDirtyTail;
+}
+
+/*
** Clear the PGHDR_NEED_SYNC flag from all dirty pages.
*/
void sqlite3PcacheClearSyncFlags(PCache *pCache){
@@ -484,7 +495,7 @@ void sqlite3PcacheTruncate(PCache *pCache, Pgno pgno){
** it must be that pgno==0.
*/
assert( p->pgno>0 );
- if( ALWAYS(p->pgno>pgno) ){
+ if( p->pgno>pgno ){
assert( p->flags&PGHDR_DIRTY );
sqlite3PcacheMakeClean(p);
}
@@ -675,6 +686,17 @@ void sqlite3PcacheShrink(PCache *pCache){
*/
int sqlite3HeaderSizePcache(void){ return ROUND8(sizeof(PgHdr)); }
+/*
+** Return the number of dirty pages currently in the cache, as a percentage
+** of the configured cache size.
+*/
+int sqlite3PCachePercentDirty(PCache *pCache){
+ PgHdr *pDirty;
+ int nDirty = 0;
+ int nCache = numberOfCachePages(pCache);
+ for(pDirty=pCache->pDirty; pDirty; pDirty=pDirty->pDirtyNext) nDirty++;
+ return nCache ? (int)(((i64)nDirty * 100) / nCache) : 0;
+}
#if defined(SQLITE_CHECK_PAGES) || defined(SQLITE_DEBUG)
/*
diff --git a/src/pcache.h b/src/pcache.h
index 475c04c06..04bce289d 100644
--- a/src/pcache.h
+++ b/src/pcache.h
@@ -99,6 +99,7 @@ void sqlite3PcacheDrop(PgHdr*); /* Remove page from cache */
void sqlite3PcacheMakeDirty(PgHdr*); /* Make sure page is marked dirty */
void sqlite3PcacheMakeClean(PgHdr*); /* Mark a single page as clean */
void sqlite3PcacheCleanAll(PCache*); /* Mark all dirty list pages as clean */
+void sqlite3PcacheClearWritable(PCache*);
/* Change a page number. Used by incr-vacuum. */
void sqlite3PcacheMove(PgHdr*, Pgno);
@@ -173,4 +174,7 @@ void sqlite3PCacheSetDefault(void);
int sqlite3HeaderSizePcache(void);
int sqlite3HeaderSizePcache1(void);
+/* Number of dirty pages as a percentage of the configured cache size */
+int sqlite3PCachePercentDirty(PCache*);
+
#endif /* _PCACHE_H_ */
diff --git a/src/test3.c b/src/test3.c
index 2a41068e5..817e8a88f 100644
--- a/src/test3.c
+++ b/src/test3.c
@@ -547,7 +547,7 @@ static int btree_from_db(
/*
** Usage: btree_ismemdb ID
**
-** Return true if the B-Tree is in-memory.
+** Return true if the B-Tree is currently stored entirely in memory.
*/
static int btree_ismemdb(
void *NotUsed,
@@ -557,6 +557,7 @@ static int btree_ismemdb(
){
Btree *pBt;
int res;
+ sqlite3_file *pFile;
if( argc!=2 ){
Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
@@ -566,7 +567,8 @@ static int btree_ismemdb(
pBt = sqlite3TestTextToPtr(argv[1]);
sqlite3_mutex_enter(pBt->db->mutex);
sqlite3BtreeEnter(pBt);
- res = sqlite3PagerIsMemdb(sqlite3BtreePager(pBt));
+ pFile = sqlite3PagerFile(sqlite3BtreePager(pBt));
+ res = (pFile->pMethods==0);
sqlite3BtreeLeave(pBt);
sqlite3_mutex_leave(pBt->db->mutex);
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(res));