diff --git a/manifest b/manifest index e09f538fbc..9be78e2ab8 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Assorted\sfixes\sto\sthe\shandling\sof\svarious\smalloc()\sfailures.\s(CVS\s2413) -D 2005-03-21T03:53:38 +C Add\sfunction\sto\srecover\sfrom\sa\smalloc()\sfailure.\s(CVS\s2414) +D 2005-03-21T04:04:02 F Makefile.in 5c00d0037104de2a50ac7647a5f12769795957a3 F Makefile.linux-gcc 06be33b2a9ad4f005a5f42b22c4a19dab3cbb5c7 F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028 @@ -30,8 +30,8 @@ F sqlite3.pc.in 985b9bf34192a549d7d370e0f0b6b34a4f61369a F src/alter.c 8ee27ba2d09f26030c145a780ec8a93cd876d315 F src/attach.c 3615dbe960cbee4aa5ea300b8a213dad36527b0f F src/auth.c 18c5a0befe20f3a58a41e3ddd78f372faeeefe1f -F src/btree.c 1d9b2179ccac13970c883da6ae3758cc72978bb0 -F src/btree.h 2e2cc923224649337d7217df0dd32b06673ca180 +F src/btree.c c33c0e6833eb8ac0f0941c1f8e722f7c70dbef57 +F src/btree.h 41a71ce027db9ddee72cb43df2316bbe3a1d92af F src/build.c 2dc89aa35a0b4aa318ff9eac941f8412fa6db5df F src/date.c 2134ef4388256e8247405178df8a61bd60dc180a F src/delete.c d70d54a84695de92efc05b9db7d3684cd21d9094 @@ -42,7 +42,7 @@ F src/hash.c 2b1b13f7400e179631c83a1be0c664608c8f021f F src/hash.h 1b0c445e1c89ff2aaad9b4605ba61375af001e84 F src/insert.c 34c25c33f51a43644a42cc091ac967b070c6b6d5 F src/legacy.c d58ea507bce885298a2c8c3cbb0f4bff5d47830b -F src/main.c 90cb84bbb85aa89442af41ad4323b136af6527b7 +F src/main.c 2062faded47289c50cdbc083e00d1aa9a872f1a1 F src/md5.c 7ae1c39044b95de2f62e066f47bb1deb880a1070 F src/os.h 0c805df3df02b98eb78a7a86756c3cbd4e190939 F src/os_common.h 0e7f428ba0a6c40a61bc56c4e96f493231301b73 @@ -52,19 +52,19 @@ F src/os_unix.c fba0167576f09e242afd4c4978e1d2944b1da8b5 F src/os_unix.h 40b2fd1d02cfa45d6c3dea25316fd019cf9fcb0c F src/os_win.c 2bbbe6fbb010763c3fa79d5e951afca9b138c6b5 F src/os_win.h 41a946bea10f61c158ce8645e7646b29d44f122b -F src/pager.c f12044be70383010510d06668e3b6c94f78a4726 -F src/pager.h 70d496f372163abb6340f474288c4bb9ea962cf7 +F src/pager.c d6ba3096969bc306c46b7db9c11cfe0bbedcc9b9 +F src/pager.h 9a417a1e04737c227ebbba3bdf8597d6dd51513a F src/parse.y 10c0ace9efce31d5a06e03488a4284b9d97abc56 F src/pragma.c 4b20dbc0f4b97f412dc511853d3d0c2e0d4adedc F src/printf.c 3d20b21cfecadacecac3fb7274e746cb81d3d357 F src/random.c eff68e3f257e05e81eae6c4d50a51eb88beb4ff3 F src/select.c 85695750854188d1eb2d82e0439c99ed6e82645e F src/shell.c 25b3217d7c64e6497225439d261a253a23efff26 -F src/sqlite.h.in c85f6bad9ca7de29f505fe886646cfff7df4c55e -F src/sqliteInt.h 9b2aa887e79b2ecadc24f0b30363b9ec1e5b51e3 +F src/sqlite.h.in 33e7201f78b3f4aa306d885b69e68fa0f0286368 +F src/sqliteInt.h ecb1592406cf4c684f0ad9ce1bc4fe2a37a61efb F src/table.c 25b3ff2b39b7d87e8d4a5da0713d68dfc06cbee9 F src/tclsqlite.c 29e56ecdb94c4066dbe6b088d12cc2404bc9597e -F src/test1.c dd3e4961f3b9b235a68d4af5d77a06eb7be73184 +F src/test1.c 13d1d2198b3267c8dc6abd22ada4a992c79acefc F src/test2.c 7f0ef466706ac01414e1136b96e5d8a65cb97545 F src/test3.c 683e1e3819152ffd35da2f201e507228921148d0 F src/test4.c 7c6b9fc33dd1f3f93c7f1ee6e5e6d016afa6c1df @@ -154,6 +154,7 @@ F test/lock2.test 59c3dd7d9b24d1bf7ec91b2d1541c37e97939d5f F test/lock3.test 615111293cf32aa2ed16d01c6611737651c96fb9 F test/main.test febb69416071134dc38b9b1971c0c2e5b0ca3ff8 F test/malloc.test b7bc72bb1627e09d6003f58de9bcd6e4be839753 +F test/malloc2.test 5375a1cd53caffd56fd06410c5bddc10f6dccded F test/memdb.test 1860e060be810bf0775bc57408a5b7c4954bcaea F test/memleak.test df2b2b96e77f8ba159a332299535b1e5f18e49ac F test/minmax.test 9429a06f1f93acf76fcacafd17160a4392e88526 @@ -172,7 +173,7 @@ F test/pagesize.test 1b826d1608fd86d2303aa895b5586052ad07eba1 F test/pragma.test 52e4ba758004e2200ff153d09c8b92f19bf940bc F test/printf.test 92ba4c510b4fc61120ffa4a01820446ed917ae57 F test/progress.test 16496001da445e6534afb94562c286708316d82f x -F test/quick.test 91e5b8ae6663dc9e3e754b271f0384f0cae706e6 +F test/quick.test 869345bbe45fdad6e3fcc0fc9ec116c0499530c3 F test/quote.test 6d75cf635d93ba2484dc9cb378d88cbae9dc2c62 F test/reindex.test 38b138abe36bf9a08c791ed44d9f76cd6b97b78b F test/rollback.test 94cd981ee3a627d9f6466f69dcf1f7dbfe695d7a @@ -277,7 +278,7 @@ F www/tclsqlite.tcl e73f8f8e5f20e8277619433f7970060ab01088fc F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0 F www/version3.tcl 092a01f5ef430d2c4acc0ae558d74c4bb89638a0 F www/whentouse.tcl 528299b8316726dbcc5548e9aa0648c8b1bd055b -P 21012bba176035ff69f860936794a6c2a8eac9df -R 277a3536d0f906527a71bca818fa2ef9 +P e7844a01c248e8d9204ea9214bec84c81dc07f32 +R f4236514887458fc97a3ba49da042ffa U danielk1977 -Z c73e8357d1c9dc50a07df66e791e69aa +Z 3885f9adee945c24942080cd5fadfb47 diff --git a/manifest.uuid b/manifest.uuid index 3e9415fd6f..48a178a3e4 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e7844a01c248e8d9204ea9214bec84c81dc07f32 \ No newline at end of file +1f9d10d7965c95d1e35f64cf4c3f128adabd41f2 \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index a83fe5636c..b2c34e533f 100644 --- a/src/btree.c +++ b/src/btree.c @@ -9,7 +9,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** $Id: btree.c,v 1.252 2005/03/14 02:01:50 drh Exp $ +** $Id: btree.c,v 1.253 2005/03/21 04:04:02 danielk1977 Exp $ ** ** This file implements a external (disk-based) database using BTrees. ** For a detailed discussion of BTrees, refer to @@ -2160,12 +2160,11 @@ int sqlite3BtreeCursor( goto create_cursor_exception; } pCur->pgnoRoot = (Pgno)iTable; + pCur->pPage = 0; /* For exit-handler, in case getAndInitPage() fails. */ if( iTable==1 && sqlite3pager_pagecount(pBt->pPager)==0 ){ rc = SQLITE_EMPTY; - pCur->pPage = 0; goto create_cursor_exception; } - pCur->pPage = 0; /* For exit-handler, in case getAndInitPage() fails. */ rc = getAndInitPage(pBt, pCur->pgnoRoot, &pCur->pPage, 0); if( rc!=SQLITE_OK ){ goto create_cursor_exception; @@ -3252,8 +3251,8 @@ static int clearCell(MemPage *pPage, unsigned char *pCell){ if( rc ) return rc; ovflPgno = get4byte(pOvfl->aData); rc = freePage(pOvfl); - if( rc ) return rc; sqlite3pager_unref(pOvfl->aData); + if( rc ) return rc; } return SQLITE_OK; } @@ -3525,7 +3524,8 @@ static int insertCell( end = cellOffset + 2*pPage->nCell + 2; ins = cellOffset + 2*i; if( end > top - sz ){ - defragmentPage(pPage); + int rc = defragmentPage(pPage); + if( rc!=SQLITE_OK ) return rc; top = get2byte(&data[hdr+5]); assert( end + sz <= top ); } @@ -4406,7 +4406,7 @@ static int balance_deeper(MemPage *pPage){ memcpy(&cdata[brk], &data[brk], usableSize-brk); assert( pChild->isInit==0 ); rc = initPage(pChild, pPage); - if( rc ) return rc; + if( rc ) goto balancedeeper_out; memcpy(pChild->aOvfl, pPage->aOvfl, pPage->nOverflow*sizeof(pPage->aOvfl[0])); pChild->nOverflow = pPage->nOverflow; if( pChild->nOverflow ){ @@ -4420,7 +4420,7 @@ static int balance_deeper(MemPage *pPage){ if( pBt->autoVacuum ){ int i; rc = ptrmapPut(pBt, pChild->pgno, PTRMAP_BTREE, pPage->pgno); - if( rc ) return rc; + if( rc ) goto balancedeeper_out; for(i=0; inCell; i++){ rc = ptrmapPutOvfl(pChild, i); if( rc!=SQLITE_OK ){ @@ -4430,6 +4430,8 @@ static int balance_deeper(MemPage *pPage){ } #endif rc = balance_nonroot(pChild); + +balancedeeper_out: releasePage(pChild); return rc; } @@ -4616,7 +4618,7 @@ int sqlite3BtreeDelete(BtCursor *pCur){ unsigned char *pNext; int szNext; int notUsed; - unsigned char *tempCell; + unsigned char *tempCell = 0; assert( !pPage->leafData ); getTempCursor(pCur, &leafCur); rc = sqlite3BtreeNext(&leafCur, ¬Used); @@ -4624,26 +4626,34 @@ int sqlite3BtreeDelete(BtCursor *pCur){ if( rc!=SQLITE_NOMEM ){ rc = SQLITE_CORRUPT; /* bkpt-CORRUPT */ } - return rc; } - rc = sqlite3pager_write(leafCur.pPage->aData); - if( rc ) return rc; - TRACE(("DELETE: table=%d delete internal from %d replace from leaf %d\n", - pCur->pgnoRoot, pPage->pgno, leafCur.pPage->pgno)); - dropCell(pPage, pCur->idx, cellSizePtr(pPage, pCell)); - pNext = findCell(leafCur.pPage, leafCur.idx); - szNext = cellSizePtr(leafCur.pPage, pNext); - assert( MX_CELL_SIZE(pBt)>=szNext+4 ); - tempCell = sqliteMallocRaw( MX_CELL_SIZE(pBt) ); - if( tempCell==0 ) return SQLITE_NOMEM; - rc = insertCell(pPage, pCur->idx, pNext-4, szNext+4, tempCell, 0); - if( rc!=SQLITE_OK ) return rc; - put4byte(findOverflowCell(pPage, pCur->idx), pgnoChild); - rc = balance(pPage, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3pager_write(leafCur.pPage->aData); + } + if( rc==SQLITE_OK ){ + TRACE(("DELETE: table=%d delete internal from %d replace from leaf %d\n", + pCur->pgnoRoot, pPage->pgno, leafCur.pPage->pgno)); + dropCell(pPage, pCur->idx, cellSizePtr(pPage, pCell)); + pNext = findCell(leafCur.pPage, leafCur.idx); + szNext = cellSizePtr(leafCur.pPage, pNext); + assert( MX_CELL_SIZE(pBt)>=szNext+4 ); + tempCell = sqliteMallocRaw( MX_CELL_SIZE(pBt) ); + if( tempCell==0 ){ + rc = SQLITE_NOMEM; + } + } + if( rc==SQLITE_OK ){ + rc = insertCell(pPage, pCur->idx, pNext-4, szNext+4, tempCell, 0); + } + if( rc==SQLITE_OK ){ + put4byte(findOverflowCell(pPage, pCur->idx), pgnoChild); + rc = balance(pPage, 0); + } + if( rc==SQLITE_OK ){ + dropCell(leafCur.pPage, leafCur.idx, szNext); + rc = balance(leafCur.pPage, 0); + } sqliteFree(tempCell); - if( rc ) return rc; - dropCell(leafCur.pPage, leafCur.idx, szNext); - rc = balance(leafCur.pPage, 0); releaseTempCursor(&leafCur); }else{ TRACE(("DELETE: table=%d delete from leaf %d\n", @@ -4651,7 +4661,9 @@ int sqlite3BtreeDelete(BtCursor *pCur){ dropCell(pPage, pCur->idx, cellSizePtr(pPage, pCell)); rc = balance(pPage, 0); } - moveToRoot(pCur); + if( rc==SQLITE_OK ){ + moveToRoot(pCur); + } return rc; } @@ -4792,7 +4804,7 @@ static int clearDatabasePage( MemPage *pParent, /* Parent page. NULL for the root */ int freePageFlag /* Deallocate page if true */ ){ - MemPage *pPage; + MemPage *pPage = 0; int rc; unsigned char *pCell; int i; @@ -4802,27 +4814,29 @@ static int clearDatabasePage( } rc = getAndInitPage(pBt, pgno, &pPage, pParent); - if( rc ) return rc; + if( rc ) goto cleardatabasepage_out; rc = sqlite3pager_write(pPage->aData); - if( rc ) return rc; + if( rc ) goto cleardatabasepage_out; for(i=0; inCell; i++){ pCell = findCell(pPage, i); if( !pPage->leaf ){ rc = clearDatabasePage(pBt, get4byte(pCell), pPage->pParent, 1); - if( rc ) return rc; + if( rc ) goto cleardatabasepage_out; } rc = clearCell(pPage, pCell); - if( rc ) return rc; + if( rc ) goto cleardatabasepage_out; } if( !pPage->leaf ){ rc = clearDatabasePage(pBt, get4byte(&pPage->aData[8]), pPage->pParent, 1); - if( rc ) return rc; + if( rc ) goto cleardatabasepage_out; } if( freePageFlag ){ rc = freePage(pPage); }else{ zeroPage(pPage, pPage->aData[0] | PTF_LEAF); } + +cleardatabasepage_out: releasePage(pPage); return rc; } @@ -4896,7 +4910,10 @@ int sqlite3BtreeDropTable(Btree *pBt, int iTable, int *piMoved){ rc = getPage(pBt, (Pgno)iTable, &pPage); if( rc ) return rc; rc = sqlite3BtreeClearTable(pBt, iTable); - if( rc ) return rc; + if( rc ){ + releasePage(pPage); + return rc; + } *piMoved = 0; @@ -5755,3 +5772,16 @@ int sqlite3BtreeSync(Btree *pBt, const char *zMaster){ } return SQLITE_OK; } + +#ifndef SQLITE_OMIT_GLOBALRECOVER +/* +** Reset the btree and underlying pager after a malloc() failure. Any +** transaction that was active when malloc() failed is rolled back. +*/ +int sqlite3BtreeReset(Btree *pBt){ + if( pBt->pCursor ) return SQLITE_BUSY; + pBt->inTrans = TRANS_NONE; + unlockBtreeIfUnused(pBt); + return sqlite3pager_reset(pBt->pPager); +} +#endif diff --git a/src/btree.h b/src/btree.h index 7426673537..19bb3d8f8d 100644 --- a/src/btree.h +++ b/src/btree.h @@ -13,7 +13,7 @@ ** subsystem. See comments in the source code for a detailed description ** of what each interface routine does. ** -** @(#) $Id: btree.h,v 1.62 2005/02/06 02:45:42 drh Exp $ +** @(#) $Id: btree.h,v 1.63 2005/03/21 04:04:03 danielk1977 Exp $ */ #ifndef _BTREE_H_ #define _BTREE_H_ @@ -73,6 +73,7 @@ int sqlite3BtreeCreateTable(Btree*, int*, int flags); int sqlite3BtreeIsInTrans(Btree*); int sqlite3BtreeIsInStmt(Btree*); int sqlite3BtreeSync(Btree*, const char *zMaster); +int sqlite3BtreeReset(Btree *); const char *sqlite3BtreeGetFilename(Btree *); const char *sqlite3BtreeGetDirname(Btree *); diff --git a/src/main.c b/src/main.c index 8e7ca89a5a..dca52977c4 100644 --- a/src/main.c +++ b/src/main.c @@ -14,7 +14,7 @@ ** other files are for internal use by SQLite and should not be ** accessed by users of the library. ** -** $Id: main.c,v 1.282 2005/03/09 12:26:51 danielk1977 Exp $ +** $Id: main.c,v 1.283 2005/03/21 04:04:03 danielk1977 Exp $ */ #include "sqliteInt.h" #include "os.h" @@ -26,6 +26,16 @@ */ const int sqlite3one = 1; +#ifndef SQLITE_OMIT_GLOBALRECOVER +/* +** Linked list of all open database handles. This is used by the +** sqlite3_global_recover() function. Entries are added to the list +** by openDatabase() and removed by sqlite3_close(). +*/ +static sqlite3 *pDbList = 0; +#endif + + /* ** Fill the InitData structure with an error message that indicates ** that the database is corrupt. @@ -514,6 +524,23 @@ int sqlite3_close(sqlite3 *db){ sqlite3ValueFree(db->pErr); } +#ifndef SQLITE_OMIT_GLOBALRECOVER + { + sqlite3 *pPrev = pDbList; + sqlite3OsEnterMutex(); + while( pPrev && pPrev->pNext!=db ){ + pPrev = pPrev->pNext; + } + if( pPrev ){ + pPrev->pNext = db->pNext; + }else{ + assert( pDbList==db ); + pDbList = db->pNext; + } + sqlite3OsLeaveMutex(); + } +#endif + db->magic = SQLITE_MAGIC_ERROR; sqliteFree(db); return SQLITE_OK; @@ -1199,6 +1226,14 @@ opendb_out: sqlite3Error(db, SQLITE_NOMEM, 0); } *ppDb = db; +#ifndef SQLITE_OMIT_GLOBALRECOVER + if( db ){ + sqlite3OsEnterMutex(); + db->pNext = pDbList; + pDbList = db; + sqlite3OsLeaveMutex(); + } +#endif return sqlite3_errcode(db); } @@ -1400,3 +1435,39 @@ int sqlite3_collation_needed16( return SQLITE_OK; } #endif /* SQLITE_OMIT_UTF16 */ + +#ifndef SQLITE_OMIT_GLOBALRECOVER +/* +** This function is called to recover from a malloc failure that occured +** within SQLite. +** +** This function is *not* threadsafe. Calling this from within a threaded +** application when threads other than the caller have used SQLite is +** dangerous and will almost certainly result in malfunctions. +*/ +int sqlite3_global_recover(){ + int rc = SQLITE_OK; + + if( sqlite3_malloc_failed ){ + sqlite3 *db; + int i; + sqlite3_malloc_failed = 0; + for(db=pDbList; db; db=db->pNext ){ + sqlite3ExpirePreparedStatements(db); + for(i=0; inDb; i++){ + Btree *pBt = db->aDb[i].pBt; + if( pBt && (rc=sqlite3BtreeReset(pBt)) ){ + goto recover_out; + } + } + db->autoCommit = 1; + } + } + +recover_out: + if( rc!=SQLITE_OK ){ + sqlite3_malloc_failed = 1; + } + return rc; +} +#endif diff --git a/src/pager.c b/src/pager.c index cfe0301713..9c6040a628 100644 --- a/src/pager.c +++ b/src/pager.c @@ -18,7 +18,7 @@ ** file simultaneously, or one process from reading the database while ** another is writing. ** -** @(#) $Id: pager.c,v 1.197 2005/03/21 03:53:38 danielk1977 Exp $ +** @(#) $Id: pager.c,v 1.198 2005/03/21 04:04:02 danielk1977 Exp $ */ #include "sqliteInt.h" #include "os.h" @@ -844,6 +844,28 @@ static void pager_reset(Pager *pPager){ assert( pPager->journalOpen==0 ); } +/* +** This function is used to reset the pager after a malloc() failure. This +** doesn't work with in-memory databases. If a malloc() fails when an +** in-memory database is in use it is not possible to recover. +** +** If a transaction or statement transaction is active, it is rolled back. +** +** It is an error to call this function if any pages are in use. +*/ +#ifndef SQLITE_OMIT_GLOBALRECOVER +int sqlite3pager_reset(Pager *pPager){ + if( pPager ){ + if( pPager->nRef || MEMDB ){ + return SQLITE_ERROR; + } + pPager->errMask &= ~(PAGER_ERR_MEM); + pager_reset(pPager); + } + return SQLITE_OK; +} +#endif + /* ** When this routine is called, the pager has the journal file open and diff --git a/src/pager.h b/src/pager.h index 8e88c86eb2..442df76a45 100644 --- a/src/pager.h +++ b/src/pager.h @@ -13,7 +13,7 @@ ** subsystem. The page cache subsystem reads and writes a file a page ** at a time and provides a journal for rollback. ** -** @(#) $Id: pager.h,v 1.41 2005/02/06 02:45:42 drh Exp $ +** @(#) $Id: pager.h,v 1.42 2005/03/21 04:04:03 danielk1977 Exp $ */ /* @@ -100,6 +100,7 @@ const char *sqlite3pager_journalname(Pager*); int sqlite3pager_rename(Pager*, const char *zNewName); void sqlite3pager_set_codec(Pager*,void(*)(void*,void*,Pgno,int),void*); int sqlite3pager_movepage(Pager*,void*,Pgno); +int sqlite3pager_reset(Pager*); #if defined(SQLITE_DEBUG) || defined(SQLITE_TEST) int sqlite3pager_lockstate(Pager*); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 430bd2cf11..613b98b7d8 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -12,7 +12,7 @@ ** This header file defines the interface that the SQLite library ** presents to client programs. ** -** @(#) $Id: sqlite.h.in,v 1.130 2005/02/05 07:33:35 danielk1977 Exp $ +** @(#) $Id: sqlite.h.in,v 1.131 2005/03/21 04:04:03 danielk1977 Exp $ */ #ifndef _SQLITE3_H_ #define _SQLITE3_H_ @@ -1215,11 +1215,32 @@ int sqlite3_expired(sqlite3_stmt*); ** is NULL pointer, then SQLite does a search for an appropriate temporary ** file directory. ** -** Once sqlite3_open() has been called, changing this variable will invalidate the -** current temporary database, if any. +** Once sqlite3_open() has been called, changing this variable will invalidate +** the current temporary database, if any. */ extern char *sqlite3_temp_directory; +/* +** This function is called to recover from a malloc() failure that occured +** within the SQLite library. Normally, after a single malloc() fails the +** library refuses to function (all major calls return SQLITE_NOMEM). +** This function library state so that it can be used again. +** +** All existing statements (sqlite3_stmt pointers) must be finalized or +** reset before this call is made. Otherwise, SQLITE_BUSY is returned. +** If any in-memory databases are in use, either as a main or TEMP +** database, SQLITE_ERROR is returned. In either of these cases, the +** library is not reset and remains unusable. +** +** This function is *not* threadsafe. Calling this from within a threaded +** application when threads other than the caller have used SQLite is +** dangerous and will almost certainly result in malfunctions. +** +** This functionality can be omitted from a build by defining the +** SQLITE_OMIT_GLOBALRECOVER at compile time. +*/ +int sqlite3_global_recover(); + #ifdef __cplusplus } /* End of the 'extern "C"' block */ #endif diff --git a/src/sqliteInt.h b/src/sqliteInt.h index f5e79a9510..3934a6b631 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -11,7 +11,7 @@ ************************************************************************* ** Internal interface definitions for SQLite. ** -** @(#) $Id: sqliteInt.h,v 1.373 2005/03/17 05:03:40 danielk1977 Exp $ +** @(#) $Id: sqliteInt.h,v 1.374 2005/03/21 04:04:03 danielk1977 Exp $ */ #ifndef _SQLITEINT_H_ #define _SQLITEINT_H_ @@ -452,6 +452,9 @@ struct sqlite3 { sqlite3_value *pErr; /* Most recent error message */ char *zErrMsg; /* Most recent error message (UTF-8 encoded) */ char *zErrMsg16; /* Most recent error message (UTF-16 encoded) */ +#ifndef SQLITE_OMIT_GLOBALRECOVER + sqlite3 *pNext; /* Linked list of open db handles. */ +#endif }; /* diff --git a/src/test1.c b/src/test1.c index 52e75217d5..62855f695d 100644 --- a/src/test1.c +++ b/src/test1.c @@ -13,7 +13,7 @@ ** is not included in the SQLite library. It is used for automated ** testing of the SQLite library. ** -** $Id: test1.c,v 1.134 2005/03/11 04:41:40 drh Exp $ +** $Id: test1.c,v 1.135 2005/03/21 04:04:02 danielk1977 Exp $ */ #include "sqliteInt.h" #include "tcl.h" @@ -2294,6 +2294,24 @@ static int test_stmt_utf8( return TCL_OK; } +static int test_global_recover( + void * clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *CONST objv[] +){ +#ifndef SQLITE_OMIT_GLOBALRECOVER + int rc; + if( objc!=1 ){ + Tcl_WrongNumArgs(interp, 1, objv, ""); + return TCL_ERROR; + } + rc = sqlite3_global_recover(); + Tcl_SetResult(interp, (char *)errorName(rc), TCL_STATIC); +#endif + return TCL_OK; +} + /* ** Usage: sqlite3_column_text STMT column ** @@ -2765,6 +2783,12 @@ static void set_options(Tcl_Interp *interp){ Tcl_SetVar2(interp, "sqlite_options", "foreignkey", "1", TCL_GLOBAL_ONLY); #endif +#ifdef SQLITE_OMIT_GLOBALRECOVER + Tcl_SetVar2(interp, "sqlite_options", "globalrecover", "0", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "globalrecover", "1", TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_OMIT_INTEGRITY_CHECK Tcl_SetVar2(interp, "sqlite_options", "integrityck", "0", TCL_GLOBAL_ONLY); #else @@ -2952,6 +2976,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){ { "sqlite3_column_decltype16", test_stmt_utf16, sqlite3_column_decltype16}, { "sqlite3_column_name16", test_stmt_utf16, sqlite3_column_name16 }, #endif + { "sqlite3_global_recover", test_global_recover, 0 }, /* Functions from os.h */ { "sqlite3OsOpenReadWrite",test_sqlite3OsOpenReadWrite, 0 }, diff --git a/test/malloc2.test b/test/malloc2.test new file mode 100644 index 0000000000..15e41bfa5f --- /dev/null +++ b/test/malloc2.test @@ -0,0 +1,335 @@ +# 2005 March 18 +# +# The author disclaims copyright to this source code. In place of +# a legal notice, here is a blessing: +# +# May you do good and not evil. +# May you find forgiveness for yourself and forgive others. +# May you share freely, never taking more than you give. +# +#*********************************************************************** +# This file attempts to check that the library can recover from a malloc() +# failure when sqlite3_global_recover() is invoked. +# +# $Id: malloc2.test,v 1.1 2005/03/21 04:04:03 danielk1977 Exp $ + +set testdir [file dirname $argv0] +source $testdir/tester.tcl + +# Only run these tests if memory debugging is turned on. +# +if {[info command sqlite_malloc_stat]==""} { + puts "Skipping malloc tests: not compiled with -DSQLITE_DEBUG..." + finish_test + return +} + +ifcapable !globalrecover { + finish_test + return +} + +# Generate a checksum based on the contents of the database. If the +# checksum of two databases is the same, and the integrity-check passes +# for both, the two databases are identical. +# +proc cksum {db} { + set ret [list] + set sql { + SELECT name FROM sqlite_master WHERE type = 'table' UNION + SELECT name FROM sqlite_temp_master WHERE type = 'table' UNION + SELECT 'sqlite_master' UNION + SELECT 'sqlite_temp_master' + } + foreach tbl [$db eval $sql] { + set cols [list] + $db eval "PRAGMA table_info($tbl)" { + lappend cols $name + } + set sql "SELECT md5sum([join $cols ,]) FROM $tbl" + lappend ret [db onecolumn $sql] + } + return $ret +} + +proc do_malloc2_test {tn args} { + array set ::mallocopts $args + set sum [cksum db] + + for {set ::n 1} {true} {incr ::n} { + + # Run the SQL. Malloc number $::n is set to fail. A malloc() failure + # may or may not be reported. + sqlite_malloc_fail $::n + do_test malloc2-$tn.$::n.2 { + set res [catchsql $::mallocopts(-sql)] + set rc [expr { + 0==[string compare $res {1 {out of memory}}] || + 0==[lindex $res 0] + }] + if {$rc!=1} { + puts "Error: $res" + } + set rc + } {1} + + # If $::n is greater than the number of malloc() calls required to + # execute the SQL, then this test is finished. Break out of the loop. + if {[lindex [sqlite_malloc_stat] 2]>0} { + sqlite_malloc_fail -1 + break + } + + # Nothing should work now, because the allocator should refuse to + # allocate any memory. + do_test malloc2-$tn.$::n.3 { + catchsql {SELECT 'nothing should work'} + } {1 {out of memory}} + + # Recover from the malloc failure. + do_test malloc2-$tn.$::n.4 { + if 0 { + db close + sqlite_malloc_fail -1 + set ::DB [sqlite3 db test.db] + set dummy SQLITE_OK + } else { + sqlite3_global_recover + } + } {SQLITE_OK} + + # Checksum the database. + do_test malloc2-$tn.$::n.5 { + cksum db + } $sum + + integrity_check malloc2-$tn.$::n.6 + if {$::nErr>1} return + } + unset ::mallocopts +} + +do_test malloc2.1.setup { + execsql { + CREATE TABLE abc(a, b, c); + INSERT INTO abc VALUES(10, 20, 30); + INSERT INTO abc VALUES(40, 50, 60); + CREATE INDEX abc_i ON abc(a, b, c); + } +} {} +do_malloc2_test 1.1 -sql { + SELECT * FROM abc; +} +do_malloc2_test 1.2 -sql { + UPDATE abc SET c = c+10; +} +do_malloc2_test 1.3 -sql { + INSERT INTO abc VALUES(70, 80, 90); +} +do_malloc2_test 1.4 -sql { + DELETE FROM abc; +} +do_test malloc2.1.5 { + execsql { + SELECT * FROM abc; + } +} {} + +do_test malloc2.2.setup { + execsql { + CREATE TABLE def(a, b, c); + CREATE INDEX def_i1 ON def(a); + CREATE INDEX def_i2 ON def(c); + BEGIN; + } + for {set i 0} {$i<20} {incr i} { + execsql { + INSERT INTO def VALUES(randstr(300,300),randstr(300,300),randstr(300,300)); + } + } + execsql { + COMMIT; + } +} {} +do_malloc2_test 2 -sql { + BEGIN; + UPDATE def SET a = randstr(100,100) WHERE (oid%9)==0; + INSERT INTO def SELECT * FROM def WHERE (oid%13)==0; + + CREATE INDEX def_i3 ON def(b); + + UPDATE def SET a = randstr(100,100) WHERE (oid%9)==1; + INSERT INTO def SELECT * FROM def WHERE (oid%13)==1; + + CREATE TABLE def2 AS SELECT * FROM def; + DROP TABLE def; + CREATE TABLE def AS SELECT * FROM def2; + DROP TABLE def2; + + DELETE FROM def WHERE (oid%9)==2; + INSERT INTO def SELECT * FROM def WHERE (oid%13)==2; + COMMIT; +} + +do_test malloc2.3.setup { + execsql { + CREATE TEMP TABLE ghi(a, b, c); + BEGIN; + } + for {set i 0} {$i<20} {incr i} { + execsql { + INSERT INTO ghi VALUES(randstr(300,300),randstr(300,300),randstr(300,300)); + } + } + execsql { + COMMIT; + } +} {} +do_malloc2_test 3 -sql { + BEGIN; + CREATE INDEX ghi_i1 ON ghi(a); + UPDATE def SET a = randstr(100,100) WHERE (oid%2)==0; + UPDATE ghi SET a = randstr(100,100) WHERE (oid%2)==0; + COMMIT; +} + +############################################################################ +# The test cases below are to increase the code coverage in btree.c and +# pager.c of this test file. The idea is that each malloc() that occurs in +# these two source files should be made to fail at least once. +# +catchsql { + DROP TABLE ghi; +} +do_malloc2_test 4.1 -sql { + SELECT * FROM def ORDER BY oid ASC; + SELECT * FROM def ORDER BY oid DESC; +} +do_malloc2_test 4.2 -sql { + PRAGMA cache_size = 10; + BEGIN; + + -- This will put about 25 pages on the free list. + DELETE FROM def WHERE 1; + + -- Allocate 32 new root pages. This will exercise the 'extract specific + -- page from the freelist' code when in auto-vacuum mode (see the + -- allocatePage() routine in btree.c). + CREATE TABLE t1(a UNIQUE, b UNIQUE, c UNIQUE); + CREATE TABLE t2(a UNIQUE, b UNIQUE, c UNIQUE); + CREATE TABLE t3(a UNIQUE, b UNIQUE, c UNIQUE); + CREATE TABLE t4(a UNIQUE, b UNIQUE, c UNIQUE); + CREATE TABLE t5(a UNIQUE, b UNIQUE, c UNIQUE); + CREATE TABLE t6(a UNIQUE, b UNIQUE, c UNIQUE); + CREATE TABLE t7(a UNIQUE, b UNIQUE, c UNIQUE); + CREATE TABLE t8(a UNIQUE, b UNIQUE, c UNIQUE); + + ROLLBACK; +} + +######################################################################## +# Test that the global linked list of database handles works. An assert() +# will fail if there is some problem. +do_test malloc2-5 { + sqlite3 db1 test.db + sqlite3 db2 test.db + sqlite3 db3 test.db + sqlite3 db4 test.db + sqlite3 db5 test.db + + # Close the head of the list: + db5 close + + # Close the end of the list: + db1 close + + # Close a handle from the middle of the list: + db3 close + + # Close the other two. Then open and close one more database, to make + # sure the head of the list was set back to NULL. + db2 close + db4 close + sqlite db1 test.db + db1 close +} {} + +######################################################################## +# Check that if a statement is active sqlite3_global_recover doesn't reset +# the sqlite3_malloc_failed variable. +do_test malloc2-6.1 { + set ::STMT [sqlite3_prepare $::DB {SELECT * FROM def} -1 DUMMY] + sqlite3_step $::STMT +} {SQLITE_ROW} +do_test malloc2-6.2 { + sqlite3 db1 test.db + sqlite_malloc_fail 100 + catchsql { + SELECT * FROM def; + } db1 +} {1 {out of memory}} +do_test malloc2-6.3 { + sqlite3_global_recover +} {SQLITE_BUSY} +do_test malloc2-6.4 { + catchsql { + SELECT 'hello'; + } +} {1 {out of memory}} +do_test malloc2-6.5 { + sqlite3_reset $::STMT +} {SQLITE_OK} +do_test malloc2-6.6 { + sqlite3_global_recover +} {SQLITE_OK} +do_test malloc2-6.7 { + catchsql { + SELECT 'hello'; + } +} {0 hello} +do_test malloc2-6.8 { + sqlite3_step $::STMT +} {SQLITE_ERROR} +do_test malloc2-6.9 { + sqlite3_finalize $::STMT +} {SQLITE_SCHEMA} +do_test malloc2-6.10 { + db1 close +} {} + +######################################################################## +# Check that if an in-memory database is being used it is not possible +# to recover from a malloc() failure. +ifcapable memorydb { + do_test malloc2-7.1 { + sqlite3 db1 :memory: + list + } {} + do_test malloc2-7.2 { + sqlite_malloc_fail 100 + catchsql { + SELECT * FROM def; + } + } {1 {out of memory}} + do_test malloc2-7.3 { + sqlite3_global_recover + } {SQLITE_ERROR} + do_test malloc2-7.4 { + catchsql { + SELECT 'hello'; + } + } {1 {out of memory}} + do_test malloc2-7.5 { + db1 close + } {} + do_test malloc2-7.6 { + sqlite3_global_recover + } {SQLITE_OK} + do_test malloc2-7.7 { + catchsql { + SELECT 'hello'; + } + } {0 hello} +} + +finish_test diff --git a/test/quick.test b/test/quick.test index 8cc30cfea0..228a12e862 100644 --- a/test/quick.test +++ b/test/quick.test @@ -10,7 +10,7 @@ #*********************************************************************** # This file runs all tests. # -# $Id: quick.test,v 1.33 2004/11/13 13:19:56 danielk1977 Exp $ +# $Id: quick.test,v 1.34 2005/03/21 04:04:03 danielk1977 Exp $ set testdir [file dirname $argv0] source $testdir/tester.tcl @@ -28,6 +28,7 @@ set EXCLUDE { corrupt.test crash.test malloc.test + malloc2.test memleak.test misuse.test quick.test