Index: src/pager.c ================================================================== --- src/pager.c +++ src/pager.c @@ -399,12 +399,13 @@ int nHash; /* Size of the pager hash table */ PgHdr **aHash; /* Hash table to map page number to PgHdr */ #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT Pager *pNext; /* Doubly linked list of pagers on which */ Pager *pPrev; /* sqlite3_release_memory() will work */ - int iInUseMM; /* Non-zero if unavailable to MM */ int iInUseDB; /* Non-zero if in sqlite3_release_memory() */ + u8 iInUseMM; /* Non-zero if unavailable to MM */ + u8 onPagerList; /* True if part of the sqlite3PagerList */ #endif char *pTmpSpace; /* Pager.pageSize bytes of space for tmp use */ char dbFileVers[16]; /* Changes whenever database file changes */ }; @@ -509,33 +510,39 @@ ** This is a special-purpose mutex. It only provides mutual exclusion ** between the Btree and the Memory Management sqlite3_release_memory() ** function. It does not prevent, for example, two Btrees from accessing ** the same pager at the same time. Other general-purpose mutexes in ** the btree layer handle that chore. +** +** The pagerMutexHeld(X) macro is for sanity checking. This macro verifies +** that the database-connection mutex is held for pager X and asserts if it +** is not. */ #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT static void pagerEnter(Pager *p){ - p->iInUseDB++; - if( p->iInUseMM && p->iInUseDB==1 ){ + if( p->iInUseDB>0 ){ + p->iInUseDB++; + }else{ #ifndef SQLITE_MUTEX_NOOP sqlite3_mutex *mutex; mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM2); #endif - p->iInUseDB = 0; sqlite3_mutex_enter(mutex); + assert( p->iInUseMM==0 ); p->iInUseDB = 1; sqlite3_mutex_leave(mutex); } - assert( p->iInUseMM==0 ); } static void pagerLeave(Pager *p){ p->iInUseDB--; assert( p->iInUseDB>=0 ); } +# define pagerMutexHeld(X) assert( (X)->iInUseDB>0 || !(X)->onPagerList ) #else # define pagerEnter(X) # define pagerLeave(X) +# define pagerMutexHeld(X) #endif /* ** Add page pPg to the end of the linked list managed by structure ** pList (pPg becomes the last entry in the list - the most recently @@ -697,10 +704,11 @@ return; } sqlite3_free(pPager->aHash); pPager->nHash = N; pPager->aHash = aHash; + pagerMutexHeld(pPager); for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ int h; if( pPg->pgno==0 ){ assert( pPg->pNextHash==0 && pPg->pPrevHash==0 ); continue; @@ -1271,10 +1279,11 @@ ** to access those pages will likely result in a coredump. */ static void pager_reset(Pager *pPager){ PgHdr *pPg, *pNext; if( pPager->errCode ) return; + pagerMutexHeld(pPager); for(pPg=pPager->pAll; pPg; pPg=pNext){ IOTRACE(("PGFREE %p %d\n", pPager, pPg->pgno)); PAGER_INCR(sqlite3_pager_pgfree_count); pNext = pPg->pNextAll; lruListRemove(pPg); @@ -1414,10 +1423,11 @@ rc = sqlite3OsDelete(pPager->pVfs, pPager->zJournal, 0); } } sqlite3BitvecDestroy(pPager->pInJournal); pPager->pInJournal = 0; + pagerMutexHeld(pPager); for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ pPg->inJournal = 0; pPg->dirty = 0; pPg->needSync = 0; pPg->alwaysRollback = 0; @@ -2371,10 +2381,11 @@ assert( sqlite3PagerList->pPrev==0 ); sqlite3PagerList->pPrev = pPager; } pPager->pPrev = 0; sqlite3PagerList = pPager; + pPager->onPagerList = 1; sqlite3_mutex_leave(mutex); } #endif return SQLITE_OK; } @@ -2635,10 +2646,11 @@ static void pager_truncate_cache(Pager *pPager){ PgHdr *pPg; PgHdr **ppPg; int dbSize = pPager->dbSize; + pagerMutexHeld(pPager); ppPg = &pPager->pAll; while( (pPg = *ppPg)!=0 ){ if( pPg->pgno<=dbSize ){ ppPg = &pPg->pNextAll; }else if( pPg->nRef>0 ){ @@ -2705,11 +2717,13 @@ if( nPage>=(unsigned)pPager->dbSize ){ return SQLITE_OK; } if( MEMDB ){ pPager->dbSize = nPage; + pagerEnter(pPager); pager_truncate_cache(pPager); + pagerLeave(pPager); return SQLITE_OK; } pagerEnter(pPager); rc = syncJournal(pPager); pagerLeave(pPager); @@ -2718,16 +2732,14 @@ } /* Get an exclusive lock on the database before truncating. */ pagerEnter(pPager); rc = pager_wait_on_lock(pPager, EXCLUSIVE_LOCK); + if( rc==SQLITE_OK ){ + rc = pager_truncate(pPager, nPage); + } pagerLeave(pPager); - if( rc!=SQLITE_OK ){ - return rc; - } - - rc = pager_truncate(pPager, nPage); return rc; } /* ** Shutdown the page cache. Free all memory and close all files. @@ -2742,10 +2754,14 @@ ** is made to roll it back. If an error occurs during the rollback ** a hot journal may be left in the filesystem but no error is returned ** to the caller. */ int sqlite3PagerClose(Pager *pPager){ + pagerEnter(pPager); + pPager->errCode = 0; + pager_reset(pPager); + pagerLeave(pPager); #ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT if( !MEMDB ){ #ifndef SQLITE_MUTEX_NOOP sqlite3_mutex *mutex = sqlite3_mutex_alloc(SQLITE_MUTEX_STATIC_MEM2); #endif @@ -2757,18 +2773,16 @@ } if( pPager->pNext ){ pPager->pNext->pPrev = pPager->pPrev; } sqlite3_mutex_leave(mutex); + pPager->onPagerList = 0; } #endif - disable_simulated_io_errors(); sqlite3FaultBeginBenign(-1); - pPager->errCode = 0; pPager->exclusiveMode = 0; - pager_reset(pPager); pagerUnlockAndRollback(pPager); enable_simulated_io_errors(); sqlite3FaultEndBenign(-1); PAGERTRACE2("CLOSE %d\n", PAGERID(pPager)); IOTRACE(("CLOSE %p\n", pPager)) @@ -2928,10 +2942,11 @@ /* 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{ + pagerMutexHeld(pPager); for(pPg=pPager->pAll; pPg; pPg=pPg->pNextAll){ assert( pPg->needSync==0 ); } assert( pPager->lru.pFirstSynced==pPager->lru.pFirst ); } @@ -3166,11 +3181,11 @@ PgHdr *pPg; *ppPg = 0; /* It is illegal to call this function unless the pager object ** pointed to by pPager has at least one free page (page with nRef==0). - */ + */ assert(!MEMDB); assert(pPager->lru.pFirst); /* Find a page to recycle. Try to locate a page that does not ** require us to do an fsync() on the journal. @@ -3277,11 +3292,10 @@ pPager->iInUseMM = 1; } while( rc==SQLITE_OK && (nReq<0 || nReleasedpPager; - assert(!pPg->needSync || pPg==pPager->lru.pFirst); - assert(pPg->needSync || pPg==pPager->lru.pFirstSynced); - + assert( pPager->iInUseDB==0 ); savedBusy = pPager->pBusyHandler; pPager->pBusyHandler = 0; - rc = pager_recycle(pPager, &pRecycled); + rc = pager_recycle(pPager, &pPg); + assert( pPager->iInUseDB==0 ); pPager->pBusyHandler = savedBusy; - assert(pRecycled==pPg || rc!=SQLITE_OK); if( rc==SQLITE_OK ){ /* We've found a page to free. At this point the page has been ** removed from the page hash-table, free-list and synced-list ** (pFirstSynced). It is still in the all pages (pAll) list. ** Remove it from this list before freeing. @@ -3615,13 +3627,16 @@ */ static int pagerAllocatePage(Pager *pPager, PgHdr **ppPg){ int rc = SQLITE_OK; PgHdr *pPg; int nByteHdr; + /* Create a new PgHdr if any of the four conditions defined - ** above are met: */ + ** above are met: + */ + pagerMutexHeld(pPager); if( pPager->nPagemxPage || pPager->lru.pFirst==0 || MEMDB || (pPager->lru.pFirstSynced==0 && pPager->doNotSync) ){ @@ -3651,15 +3666,17 @@ goto pager_allocate_out; } memset(pPg, 0, nByteHdr); pPg->pData = pData; pPg->pPager = pPager; + pagerMutexHeld(pPager); pPg->pNextAll = pPager->pAll; pPager->pAll = pPg; pPager->nPage++; }else{ /* Recycle an existing page with a zero ref-count. */ + pagerMutexHeld(pPager); rc = pager_recycle(pPager, &pPg); if( rc==SQLITE_BUSY ){ rc = SQLITE_IOERR_BLOCKED; } if( rc!=SQLITE_OK ){ @@ -4702,10 +4719,11 @@ } } #endif rc = writeMasterJournal(pPager, zMaster); if( rc!=SQLITE_OK ) goto sync_exit; + pagerMutexHeld(pPager); rc = syncJournal(pPager); } if( rc!=SQLITE_OK ) goto sync_exit; #ifndef SQLITE_OMIT_AUTOVACUUM @@ -4829,10 +4847,11 @@ int sqlite3PagerRollback(Pager *pPager){ int rc; PAGERTRACE2("ROLLBACK %d\n", PAGERID(pPager)); if( MEMDB ){ PgHdr *p; + pagerMutexHeld(pPager); for(p=pPager->pAll; p; p=p->pNextAll){ PgHistory *pHist; assert( !p->alwaysRollback ); if( !p->dirty ){ assert( !((PgHistory *)PGHDR_TO_HIST(p, pPager))->pOrig );